diff --git a/3rdparty/ext_exiv2/CMakeLists.txt b/3rdparty/ext_exiv2/CMakeLists.txt index 29c51c7ed6..8c971f7221 100644 --- a/3rdparty/ext_exiv2/CMakeLists.txt +++ b/3rdparty/ext_exiv2/CMakeLists.txt @@ -1,16 +1,17 @@ SET(PREFIX_ext_exiv2 "${EXTPREFIX}" ) ExternalProject_Add( ext_exiv2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz URL_MD5 5399e3b570d7f9205f0e76d47582da4c PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/tzname.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch - + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/disable_exiv_apps.diff + INSTALL_DIR ${PREFIX_ext_exiv2} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_exiv2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DEXIV2_ENABLE_BUILD_SAMPLES=OFF -DEXIV2_ENABLE_BUILD_PO=OFF -DEXIV2_ENABLE_NLS=OFF -DICONV_INCLUDE_DIR=${PREFIX_ext_exiv2}/include UPDATE_COMMAND "" DEPENDS ext_iconv ext_expat ) diff --git a/3rdparty/ext_exiv2/disable_exiv_apps.diff b/3rdparty/ext_exiv2/disable_exiv_apps.diff new file mode 100644 index 0000000000..0a582b21c8 --- /dev/null +++ b/3rdparty/ext_exiv2/disable_exiv_apps.diff @@ -0,0 +1,35 @@ +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index aecd621..d879cf8 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -315,14 +315,14 @@ msvc_runtime_configure(${EXIV2_ENABLE_SHARED} ${EXIV2_ENABLE_DYNAMIC_RUNTIME}) + + # ****************************************************************************** + # exiv2 application +-ADD_EXECUTABLE( exiv2 ${EXIV2_SRC} ${EXIV2_HDR} ) +-TARGET_LINK_LIBRARIES( exiv2 exiv2lib ) +-INSTALL( TARGETS exiv2 ${INSTALL_TARGET_STANDARD_ARGS} ) ++#ADD_EXECUTABLE( exiv2 ${EXIV2_SRC} ${EXIV2_HDR} ) ++#TARGET_LINK_LIBRARIES( exiv2 exiv2lib ) ++#INSTALL( TARGETS exiv2 ${INSTALL_TARGET_STANDARD_ARGS} ) + + # ****************************************************************************** + # connection test application +-ADD_EXECUTABLE( conntest ${CONNTEST} ) +-TARGET_LINK_LIBRARIES( conntest ${PRIVATE_VAR} exiv2lib ${CURL_LIBRARIES} ${SSH_LIBRARIES}) ++#ADD_EXECUTABLE( conntest ${CONNTEST} ) ++#TARGET_LINK_LIBRARIES( conntest ${PRIVATE_VAR} exiv2lib ${CURL_LIBRARIES} ${SSH_LIBRARIES}) + + # ****************************************************************************** + # exifprint application +@@ -331,8 +331,8 @@ TARGET_LINK_LIBRARIES( conntest ${PRIVATE_VAR} exiv2lib ${CURL_LIBRARIES} ${SSH + + # ****************************************************************************** + # remotetest application +-ADD_EXECUTABLE( remotetest ${REMOTETEST} ) +-TARGET_LINK_LIBRARIES( remotetest exiv2lib ) ++#ADD_EXECUTABLE( remotetest ${REMOTETEST} ) ++#TARGET_LINK_LIBRARIES( remotetest exiv2lib ) + + # ****************************************************************************** + # Headers diff --git a/3rdparty/ext_quazip/CMakeLists.txt b/3rdparty/ext_quazip/CMakeLists.txt index 5819b107c4..832353aa75 100644 --- a/3rdparty/ext_quazip/CMakeLists.txt +++ b/3rdparty/ext_quazip/CMakeLists.txt @@ -1,12 +1,13 @@ SET(PREFIX_ext_quazip "${EXTPREFIX}" ) ExternalProject_Add( ext_quazip DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/stachenov/quazip/archive/0.7.6.zip URL_MD5 a3335649c34053385d8390dd1a6f1ca4 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/find_quazip.diff + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/liblocation.diff INSTALL_DIR ${PREFIX_ext_quazip} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_quazip} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} UPDATE_COMMAND "" - DEPENDS ext_zlib ext_qt + DEPENDS ext_zlib ) diff --git a/3rdparty/ext_quazip/liblocation.diff b/3rdparty/ext_quazip/liblocation.diff new file mode 100644 index 0000000000..1bdc03fc11 --- /dev/null +++ b/3rdparty/ext_quazip/liblocation.diff @@ -0,0 +1,17 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 5d8a5cc..d53548a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -54,7 +54,11 @@ endif(UNIX OR MINGW) + set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}) + + set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)") +-set(LIB_DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE STRING "Library directory name" FORCE) ++if (WIN32) ++ set(LIB_DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" CACHE STRING "Library directory name" FORCE) ++else() ++ set(LIB_DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE STRING "Library directory name" FORCE) ++endif() + set(QUAZIP_LIB_TARGET_NAME quazip${QUAZIP_LIB_VERSION_SUFFIX} CACHE + INTERNAL "Target name of libquazip" FORCE) + diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index c3b6f56cb4..5efba977e5 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,769 +1,783 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=!PATH!;!GETTEXT_SEARCH_PATH! ) if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps pushd %DEPS_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) echo Running CMake for deps... "%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% if errorlevel 1 ( echo ERROR: CMake configure failed! 1>&2 exit /b 104 ) echo. set EXT_TARGETS=patch png2ico zlib lzma gettext qt boost eigen3 exiv2 fftw3 ilmbase set EXT_TARGETS=%EXT_TARGETS% jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt set EXT_TARGETS=%EXT_TARGETS% quazip for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... +echo "%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ + -DBoost_DEBUG=OFF ^ + -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ + -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ + -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ + -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ + -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ + -DBUILD_TESTING=OFF ^ + -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ + -DFOUNDATION_BUILD=ON ^ + -Wno-dev ^ + -G "MinGW Makefiles" ^ + -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% + "%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ -DBoost_DEBUG=OFF ^ -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ -DBUILD_TESTING=OFF ^ -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ -DFOUNDATION_BUILD=ON ^ -Wno-dev ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% if errorlevel 1 ( echo ERROR: CMake configure failed! 1>&2 exit /b 104 ) echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/cmake/modules/FindQuaZip.cmake b/cmake/modules/FindQuaZip.cmake index 23f4918db4..7e628fcd4b 100644 --- a/cmake/modules/FindQuaZip.cmake +++ b/cmake/modules/FindQuaZip.cmake @@ -1,43 +1,43 @@ # QUAZIP_FOUND - QuaZip library was found # QUAZIP_INCLUDE_DIR - Path to QuaZip include dir # QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR) # QUAZIP_LIBRARIES - List of QuaZip libraries # QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers IF (QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) # in cache already SET(QUAZIP_FOUND TRUE) ELSE (QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) IF (Qt5Core_FOUND) set(QUAZIP_LIB_VERSION_SUFFIX 5) ENDIF() IF (WIN32) FIND_PATH(QUAZIP_LIBRARY_DIR WIN32_DEBUG_POSTFIX d NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS "C:/Programme/" "C:/Program Files" PATH_SUFFIXES QuaZip/lib ) FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR}) - FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip) + FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip${QUAZIP_LIB_VERSION_SUFFIX}) FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h) ELSE(WIN32) FIND_PACKAGE(PkgConfig) # pkg_check_modules(PC_QCA2 QUIET qca2) pkg_check_modules(PC_QUAZIP quazip) FIND_LIBRARY(QUAZIP_LIBRARIES WIN32_DEBUG_POSTFIX d NAMES quazip${QUAZIP_LIB_VERSION_SUFFIX} HINTS /usr/lib /usr/lib64 ) FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h HINTS /usr/include /usr/local/include PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX} ) FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include) ENDIF (WIN32) INCLUDE(FindPackageHandleStandardArgs) SET(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR}) find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS) ENDIF (QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) diff --git a/libs/flake/KoToolBase.cpp b/libs/flake/KoToolBase.cpp index 0e097ab5c5..66fc5910d5 100644 --- a/libs/flake/KoToolBase.cpp +++ b/libs/flake/KoToolBase.cpp @@ -1,374 +1,377 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Thomas Zander * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "KoToolBase.h" #include "KoToolBase_p.h" #include "KoCanvasBase.h" #include "KoPointerEvent.h" #include "KoDocumentResourceManager.h" #include "KoCanvasResourceProvider.h" #include "KoViewConverter.h" #include "KoShapeController.h" #include "KoShapeControllerBase.h" #include "KoToolSelection.h" #include "KoCanvasController.h" #include #include #include #include #include #include KoToolBase::KoToolBase(KoCanvasBase *canvas) : d_ptr(new KoToolBasePrivate(this, canvas)) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::KoToolBase(KoToolBasePrivate &dd) : d_ptr(&dd) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::~KoToolBase() { qDeleteAll(d_ptr->optionWidgets); delete d_ptr; } bool KoToolBase::isActivated() const { Q_D(const KoToolBase); return d->isActivated; } void KoToolBase::activate(KoToolBase::ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); Q_D(KoToolBase); d->isActivated = true; } void KoToolBase::deactivate() { Q_D(KoToolBase); d->isActivated = false; } void KoToolBase::canvasResourceChanged(int key, const QVariant & res) { Q_UNUSED(key); Q_UNUSED(res); } void KoToolBase::documentResourceChanged(int key, const QVariant &res) { Q_UNUSED(key); Q_UNUSED(res); } bool KoToolBase::wantsAutoScroll() const { return true; } void KoToolBase::mouseDoubleClickEvent(KoPointerEvent *event) { event->ignore(); } void KoToolBase::mouseTripleClickEvent(KoPointerEvent *event) { event->ignore(); } void KoToolBase::keyPressEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::keyReleaseEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::explicitUserStrokeEndRequest() { } QVariant KoToolBase::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &) const { Q_D(const KoToolBase); if (d->canvas->canvasWidget() == 0) return QVariant(); switch (query) { case Qt::ImMicroFocus: return QRect(d->canvas->canvasWidget()->width() / 2, 0, 1, d->canvas->canvasWidget()->height()); case Qt::ImFont: return d->canvas->canvasWidget()->font(); default: return QVariant(); } } void KoToolBase::inputMethodEvent(QInputMethodEvent * event) { if (! event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); } event->accept(); } void KoToolBase::customPressEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customReleaseEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customMoveEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::useCursor(const QCursor &cursor) { Q_D(KoToolBase); d->currentCursor = cursor; emit cursorChanged(d->currentCursor); } QList > KoToolBase::optionWidgets() { Q_D(KoToolBase); if (!d->optionWidgetsCreated) { d->optionWidgets = createOptionWidgets(); d->optionWidgetsCreated = true; } return d->optionWidgets; } QAction *KoToolBase::action(const QString &name) const { Q_D(const KoToolBase); - return d->canvas->canvasController()->actionCollection()->action(name); + if (d->canvas && d->canvas->canvasController() && d->canvas->canvasController()) { + return d->canvas->canvasController()->actionCollection()->action(name); + } + return 0; } QWidget * KoToolBase::createOptionWidget() { return 0; } QList > KoToolBase::createOptionWidgets() { QList > ow; if (QWidget *widget = createOptionWidget()) { if (widget->objectName().isEmpty()) { widget->setObjectName(toolId()); } ow.append(widget); } return ow; } void KoToolBase::setToolId(const QString &id) { Q_D(KoToolBase); d->toolId = id; } QString KoToolBase::toolId() const { Q_D(const KoToolBase); return d->toolId; } QCursor KoToolBase::cursor() const { Q_D(const KoToolBase); return d->currentCursor; } void KoToolBase::deleteSelection() { } void KoToolBase::cut() { copy(); deleteSelection(); } QMenu *KoToolBase::popupActionsMenu() { return 0; } KoCanvasBase * KoToolBase::canvas() const { Q_D(const KoToolBase); return d->canvas; } void KoToolBase::setStatusText(const QString &statusText) { emit statusTextChanged(statusText); } uint KoToolBase::handleRadius() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->handleRadius(); } else { return 3; } } uint KoToolBase::grabSensitivity() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->grabSensitivity(); } else { return 3; } } QRectF KoToolBase::handleGrabRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*grabSensitivity(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } QRectF KoToolBase::handlePaintRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*handleRadius(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } void KoToolBase::setTextMode(bool value) { Q_D(KoToolBase); d->isInTextMode=value; } bool KoToolBase::paste() { return false; } void KoToolBase::copy() const { } void KoToolBase::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } void KoToolBase::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); } void KoToolBase::dropEvent(QDropEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } bool KoToolBase::hasSelection() { KoToolSelection *sel = selection(); return (sel && sel->hasSelection()); } KoToolSelection *KoToolBase::selection() { return 0; } void KoToolBase::repaintDecorations() { } bool KoToolBase::isInTextMode() const { Q_D(const KoToolBase); return d->isInTextMode; } void KoToolBase::requestUndoDuringStroke() { /** * Default implementation just cancels the stroke */ requestStrokeCancellation(); } void KoToolBase::requestStrokeCancellation() { } void KoToolBase::requestStrokeEnd() { } bool KoToolBase::maskSyntheticEvents() const { Q_D(const KoToolBase); return d->maskSyntheticEvents; } void KoToolBase::setMaskSyntheticEvents(bool value) { Q_D(KoToolBase); d->maskSyntheticEvents = value; } diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp index 20ba9d5955..dae2d081c9 100644 --- a/libs/flake/tools/KoPathTool.cpp +++ b/libs/flake/tools/KoPathTool.cpp @@ -1,1303 +1,1308 @@ /* This file is part of the KDE project * Copyright (C) 2006-2012 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007, 2010 Thomas Zander * Copyright (C) 2007 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 "KoPathTool.h" #include "KoToolBase_p.h" #include "KoPathShape_p.h" #include "KoPathToolHandle.h" #include "KoCanvasBase.h" #include "KoShapeManager.h" #include "KoSelectedShapesProxy.h" #include "KoDocumentResourceManager.h" #include "KoViewConverter.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "commands/KoPathPointTypeCommand.h" #include "commands/KoPathPointInsertCommand.h" #include "commands/KoPathPointRemoveCommand.h" #include "commands/KoPathSegmentTypeCommand.h" #include "commands/KoPathBreakAtPointCommand.h" #include "commands/KoPathSegmentBreakCommand.h" #include "commands/KoParameterToPathCommand.h" #include "commands/KoSubpathJoinCommand.h" #include #include #include #include "KoParameterShape.h" #include #include "KoPathPoint.h" #include "KoPathPointRubberSelectStrategy.h" #include "KoPathSegmentChangeStrategy.h" #include "KoPathConnectionPointStrategy.h" #include "KoParameterChangeStrategy.h" #include "PathToolOptionWidget.h" #include "KoConnectionShape.h" #include "KoSnapGuide.h" #include "KoShapeController.h" #include "kis_action_registry.h" #include #include #include "kis_command_utils.h" #include "kis_pointer_utils.h" #include #include #include #include #include #include #include #include #include static const unsigned char needle_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e, 0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00 }; static const unsigned char needle_move_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e, 0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00 }; // helper function to calculate the squared distance between two points qreal squaredDistance(const QPointF& p1, const QPointF &p2) { qreal dx = p1.x()-p2.x(); qreal dy = p1.y()-p2.y(); return dx*dx + dy*dy; } struct KoPathTool::PathSegment { PathSegment() : path(0), segmentStart(0), positionOnSegment(0) { } bool isValid() { return path && segmentStart; } KoPathShape *path; KoPathPoint *segmentStart; qreal positionOnSegment; }; KoPathTool::KoPathTool(KoCanvasBase *canvas) - : KoToolBase(canvas) - , m_pointSelection(this) - , m_activeHandle(0) - , m_handleRadius(3) - , m_activeSegment(0) - , m_currentStrategy(0) - , m_activatedTemporarily(false) + : KoToolBase(canvas) + , m_pointSelection(this) + , m_activeHandle(0) + , m_handleRadius(3) + , m_activeSegment(0) + , m_currentStrategy(0) + , m_activatedTemporarily(false) { m_points = new QActionGroup(this); // m_pointTypeGroup->setExclusive(true); m_actionPathPointCorner = action("pathpoint-corner"); - m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner); - m_points->addAction(m_actionPathPointCorner); - + if (m_actionPathPointCorner) { + m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner); + m_points->addAction(m_actionPathPointCorner); + } m_actionPathPointSmooth = action("pathpoint-smooth"); - m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth); - m_points->addAction(m_actionPathPointSmooth); + if (m_actionPathPointSmooth) { + m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth); + m_points->addAction(m_actionPathPointSmooth); + } m_actionPathPointSymmetric = action("pathpoint-symmetric"); - m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric); - m_points->addAction(m_actionPathPointSymmetric); + if (m_actionPathPointSymmetric) { + m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric); + m_points->addAction(m_actionPathPointSymmetric); + } m_actionCurvePoint = action("pathpoint-curve"); m_actionLinePoint = action("pathpoint-line"); m_actionLineSegment = action("pathsegment-line"); m_actionCurveSegment = action("pathsegment-curve"); m_actionAddPoint = action("pathpoint-insert"); m_actionRemovePoint = action("pathpoint-remove"); m_actionBreakPoint = action("path-break-point"); m_actionBreakSegment = action("path-break-segment"); m_actionJoinSegment = action("pathpoint-join"); m_actionMergePoints = action("pathpoint-merge"); m_actionConvertToPath = action("convert-to-path"); m_contextMenu.reset(new QMenu()); QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits); QBitmap m = b.createHeuristicMask(false); m_selectCursor = QCursor(b, m, 2, 0); b = QBitmap::fromData(QSize(16, 16), needle_move_bits); m = b.createHeuristicMask(false); m_moveCursor = QCursor(b, m, 2, 0); } KoPathTool::~KoPathTool() { delete m_activeHandle; delete m_activeSegment; delete m_currentStrategy; } QList > KoPathTool::createOptionWidgets() { QList > list; PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this); connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int))); connect(this, SIGNAL(singleShapeChanged(KoPathShape*)), toolOptions, SLOT(setCurrentShape(KoPathShape*))); connect(toolOptions, SIGNAL(sigRequestUpdateActions()), this, SLOT(updateActions())); updateOptionsWidget(); toolOptions->setWindowTitle(i18n("Edit Shape")); list.append(toolOptions); return list; } void KoPathTool::pointTypeChanged(QAction *type) { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); KUndo2Command *initialConversionCommand = createPointToCurveCommand(selectedPoints); // conversion should happen before the c-tor // of KoPathPointTypeCommand is executed! if (initialConversionCommand) { initialConversionCommand->redo(); } KUndo2Command *command = - new KoPathPointTypeCommand(selectedPoints, - static_cast(type->data().toInt())); + new KoPathPointTypeCommand(selectedPoints, + static_cast(type->data().toInt())); if (initialConversionCommand) { using namespace KisCommandUtils; CompositeCommand *parent = new CompositeCommand(); parent->setText(command->text()); parent->addCommand(new SkipFirstRedoWrapper(initialConversionCommand)); parent->addCommand(command); command = parent; } d->canvas->addCommand(command); } } void KoPathTool::insertPoints() { Q_D(KoToolBase); QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { qreal positionInSegment = 0.5; if (m_activeSegment && m_activeSegment->isValid()) { positionInSegment = m_activeSegment->positionOnSegment; } KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, positionInSegment); d->canvas->addCommand(cmd); // TODO: this construction is dangerous. The canvas can remove the command right after // it has been added to it! m_pointSelection.clear(); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } } } void KoPathTool::removePoints() { Q_D(KoToolBase); if (m_pointSelection.size() > 0) { KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController()); PointHandle *pointHandle = dynamic_cast(m_activeHandle); if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) { delete m_activeHandle; m_activeHandle = 0; } clearActivePointSelectionReferences(); d->canvas->addCommand(cmd); } } void KoPathTool::pointToLine() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (point->activeControlPoint1() || point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line)); } } } void KoPathTool::pointToCurve() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); KUndo2Command *command = createPointToCurveCommand(selectedPoints); if (command) { d->canvas->addCommand(command); } } } KUndo2Command* KoPathTool::createPointToCurveCommand(const QList &points) { KUndo2Command *command = 0; QList pointToChange; QList::const_iterator it(points.constBegin()); for (; it != points.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2())) pointToChange.append(*it); } if (!pointToChange.isEmpty()) { command = new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve); } return command; } void KoPathTool::segmentToLine() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line)); } } } void KoPathTool::segmentToCurve() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve)); } } } void KoPathTool::convertToPath() { Q_D(KoToolBase); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); QList parameterShapes; Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameteric = dynamic_cast(shape); if (parameteric && parameteric->isParametricShape()) { parameterShapes.append(parameteric); } } if (!parameterShapes.isEmpty()) { d->canvas->addCommand(new KoParameterToPathCommand(parameterShapes)); } QList textShapes; Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) { if (KoSvgTextShape *text = dynamic_cast(shape)) { textShapes.append(text); } } if (!textShapes.isEmpty()) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Convert to Path")); // TODO: reuse the text from KoParameterToPathCommand const QList oldSelectedShapes = implicitCastList(textShapes); new KoKeepShapesSelectedCommand(oldSelectedShapes, {}, canvas()->selectedShapesProxy(), KisCommandUtils::FlipFlopCommand::State::INITIALIZING, cmd); QList newSelectedShapes; Q_FOREACH (KoSvgTextShape *shape, textShapes) { const QPainterPath path = shape->textOutline(); if (path.isEmpty()) continue; KoPathShape *pathShape = KoPathShape::createShapeFromPainterPath(path); pathShape->setBackground(shape->background()); pathShape->setStroke(shape->stroke()); pathShape->setZIndex(shape->zIndex()); pathShape->setTransformation(shape->transformation()); KoShapeContainer *parent = shape->parent(); canvas()->shapeController()->addShapeDirect(pathShape, parent, cmd); newSelectedShapes << pathShape; } canvas()->shapeController()->removeShapes(oldSelectedShapes, cmd); new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), KisCommandUtils::FlipFlopCommand::State::FINALIZING, cmd); canvas()->addCommand(cmd); } updateOptionsWidget(); } namespace { bool checkCanJoinToPoints(const KoPathPointData & pd1, const KoPathPointData & pd2) { const KoPathPointIndex & index1 = pd1.pointIndex; const KoPathPointIndex & index2 = pd2.pointIndex; KoPathShape *path1 = pd1.pathShape; KoPathShape *path2 = pd2.pathShape; // check if subpaths are already closed if (path1->isClosedSubpath(index1.first) || path2->isClosedSubpath(index2.first)) return false; // check if first point is an endpoint if (index1.second != 0 && index1.second != path1->subpathPointCount(index1.first)-1) return false; // check if second point is an endpoint if (index2.second != 0 && index2.second != path2->subpathPointCount(index2.first)-1) return false; return true; } } void KoPathTool::mergePointsImpl(bool doJoin) { Q_D(KoToolBase); if (m_pointSelection.size() != 2) return; QList pointData = m_pointSelection.selectedPointsData(); if (pointData.size() != 2) return; const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); if (!checkCanJoinToPoints(pd1, pd2)) { return; } clearActivePointSelectionReferences(); KUndo2Command *cmd = 0; if (doJoin) { cmd = new KoMultiPathPointJoinCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection()); } else { cmd = new KoMultiPathPointMergeCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection()); } d->canvas->addCommand(cmd); } void KoPathTool::joinPoints() { mergePointsImpl(true); } void KoPathTool::mergePoints() { mergePointsImpl(false); } void KoPathTool::breakAtPoint() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData())); } } void KoPathTool::breakAtSegment() { Q_D(KoToolBase); // only try to break a segment when 2 points of the same object are selected if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0))); } } } void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoToolBase); Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { KisHandlePainterHelper helper = - KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); + KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::primarySelection()); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { parameterShape->paintHandles(helper); } else { shape->paintPoints(helper); } if (!shape->stroke() || !shape->stroke()->isVisible()) { helper.setHandleStyle(KisHandleStyle::secondarySelection()); helper.drawPath(shape->outline()); } } if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } m_pointSelection.paint(painter, converter, m_handleRadius); if (m_activeHandle) { if (m_activeHandle->check(m_pointSelection.selectedShapes())) { m_activeHandle->paint(painter, converter, m_handleRadius); } else { delete m_activeHandle; m_activeHandle = 0; } } else if (m_activeSegment && m_activeSegment->isValid()) { KoPathShape *shape = m_activeSegment->path; // if the stroke is invisible, then we already painted the outline of the shape! if (shape->stroke() && shape->stroke()->isVisible()) { KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart); KoPathSegment segment = shape->segmentByIndex(index).toCubic(); KIS_SAFE_ASSERT_RECOVER_RETURN(segment.isValid()); KisHandlePainterHelper helper = - KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); + KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::secondarySelection()); QPainterPath path; path.moveTo(segment.first()->point()); path.cubicTo(segment.first()->controlPoint2(), segment.second()->controlPoint1(), segment.second()->point()); helper.drawPath(path); } } if (m_currentStrategy) { painter.save(); KoShape::applyConversion(painter, converter); d->canvas->snapGuide()->paint(painter, converter); painter.restore(); } } void KoPathTool::repaintDecorations() { Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) { repaint(shape->boundingRect()); } m_pointSelection.repaint(); updateOptionsWidget(); } void KoPathTool::mousePressEvent(KoPointerEvent *event) { // we are moving if we hit a point and use the left mouse button event->ignore(); if (m_activeHandle) { m_currentStrategy = m_activeHandle->handleMousePress(event); event->accept(); } else { if (event->button() & Qt::LeftButton) { // check if we hit a path segment if (m_activeSegment && m_activeSegment->isValid()) { KoPathShape *shape = m_activeSegment->path; KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart); KoPathSegment segment = shape->segmentByIndex(index); m_pointSelection.add(segment.first(), !(event->modifiers() & Qt::ShiftModifier)); m_pointSelection.add(segment.second(), false); KoPathPointData data(shape, index); m_currentStrategy = new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment); event->accept(); } else { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = shapeManager->selection(); KoShape *shape = shapeManager->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } else { KIS_ASSERT_RECOVER_RETURN(m_currentStrategy == 0); m_currentStrategy = new KoPathPointRubberSelectStrategy(this, event->point); event->accept(); } } } } } void KoPathTool::mouseMoveEvent(KoPointerEvent *event) { if (event->button() & Qt::RightButton) return; if (m_currentStrategy) { m_lastPoint = event->point; m_currentStrategy->handleMouseMove(event->point, event->modifiers()); // repaint new handle positions m_pointSelection.repaint(); if (m_activeHandle) { m_activeHandle->repaint(); } if (m_activeSegment) { repaintSegment(m_activeSegment); } return; } if (m_activeSegment) { KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart); KoPathSegment segment = m_activeSegment->path->segmentByIndex(index); repaint(segment.boundingRect()); delete m_activeSegment; m_activeSegment = 0; } Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF roi = handleGrabRect(shape->documentToShape(event->point)); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { int handleId = parameterShape->handleIdAt(roi); if (handleId != -1) { useCursor(m_moveCursor); emit statusTextChanged(i18n("Drag to move handle.")); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; if (KoConnectionShape * connectionShape = dynamic_cast(parameterShape)) { //debugFlake << "handleId" << handleId; m_activeHandle = new ConnectionHandle(this, connectionShape, handleId); m_activeHandle->repaint(); return; } else { //debugFlake << "handleId" << handleId; m_activeHandle = new ParameterHandle(this, parameterShape, handleId); m_activeHandle->repaint(); return; } } } else { QList points = shape->pointsAt(roi); if (! points.empty()) { // find the nearest control point from all points within the roi KoPathPoint * bestPoint = 0; KoPathPoint::PointType bestPointType = KoPathPoint::Node; qreal minDistance = HUGE_VAL; Q_FOREACH (KoPathPoint *p, points) { // the node point must be hit if the point is not selected yet if (! m_pointSelection.contains(p) && ! roi.contains(p->point())) continue; // check for the control points first as otherwise it is no longer // possible to change the control points when they are the same as the point if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) { qreal dist = squaredDistance(roi.center(), p->controlPoint1()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint1; minDistance = dist; } } if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) { qreal dist = squaredDistance(roi.center(), p->controlPoint2()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint2; minDistance = dist; } } // check the node point at last qreal dist = squaredDistance(roi.center(), p->point()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::Node; minDistance = dist; } } if (! bestPoint) return; useCursor(m_moveCursor); if (bestPointType == KoPathPoint::Node) emit statusTextChanged(i18n("Drag to move point. Shift click to change point type.")); else emit statusTextChanged(i18n("Drag to move control point.")); PointHandle *prev = dynamic_cast(m_activeHandle); if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType) return; // no change; if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = new PointHandle(this, bestPoint, bestPointType); m_activeHandle->repaint(); return; } } } useCursor(m_selectCursor); if (m_activeHandle) { m_activeHandle->repaint(); } delete m_activeHandle; m_activeHandle = 0; PathSegment *hoveredSegment = segmentAtPoint(event->point); if(hoveredSegment) { useCursor(Qt::PointingHandCursor); emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point.")); m_activeSegment = hoveredSegment; repaintSegment(m_activeSegment); } else { uint selectedPointCount = m_pointSelection.size(); if (selectedPointCount == 0) emit statusTextChanged(QString()); else if (selectedPointCount == 1) emit statusTextChanged(i18n("Press B to break path at selected point.")); else emit statusTextChanged(i18n("Press B to break path at selected segments.")); } } void KoPathTool::repaintSegment(PathSegment *pathSegment) { if (!pathSegment || !pathSegment->isValid()) return; KoPathPointIndex index = pathSegment->path->pathPointIndex(pathSegment->segmentStart); KoPathSegment segment = pathSegment->path->segmentByIndex(index); repaint(segment.boundingRect()); } void KoPathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { const bool hadNoSelection = !m_pointSelection.hasSelection(); m_currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); if (hadNoSelection && dynamic_cast(m_currentStrategy) && !m_pointSelection.hasSelection()) { // the click didn't do anything at all. Allow it to be used by others. event->ignore(); } delete m_currentStrategy; m_currentStrategy = 0; } } void KoPathTool::keyPressEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers()); } break; case Qt::Key_Escape: m_currentStrategy->cancelInteraction(); delete m_currentStrategy; m_currentStrategy = 0; break; default: event->ignore(); return; } } else { switch (event->key()) { #ifndef NDEBUG case Qt::Key_D: if (m_pointSelection.objectCount() == 1) { QList selectedPoints = m_pointSelection.selectedPointsData(); KoPathShapePrivate *p = static_cast(selectedPoints[0].pathShape->priv()); p->debugPath(); } break; #endif case Qt::Key_B: if (m_pointSelection.size() == 1) breakAtPoint(); else if (m_pointSelection.size() >= 2) breakAtSegment(); break; default: event->ignore(); return; } } event->accept(); } void KoPathTool::keyReleaseEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier); } break; default: break; } } event->accept(); } void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_D(KoToolBase); event->ignore(); // check if we are doing something else at the moment if (m_currentStrategy) return; if (!m_activeHandle && m_activeSegment && m_activeSegment->isValid()) { QList segments; segments.append( - KoPathPointData(m_activeSegment->path, - m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart))); + KoPathPointData(m_activeSegment->path, + m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart))); KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, m_activeSegment->positionOnSegment); d->canvas->addCommand(cmd); m_pointSelection.clear(); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); event->accept(); } else if (!m_activeHandle && !m_activeSegment && m_activatedTemporarily) { emit done(); event->accept(); } else if (!m_activeHandle && !m_activeSegment) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = shapeManager->selection(); selection->deselectAll(); event->accept(); } } KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point) { // the max allowed distance from a segment const QRectF grabRoi = handleGrabRect(point); const qreal distanceThreshold = 0.5 * KisAlgebra2D::maxDimension(grabRoi); QScopedPointer segment(new PathSegment); Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; // convert document point to shape coordinates const QPointF p = shape->documentToShape(point); // our region of interest, i.e. a region around our mouse position const QRectF roi = shape->documentToShape(grabRoi); qreal minDistance = std::numeric_limits::max(); // check all segments of this shape which intersect the region of interest const QList segments = shape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { const qreal nearestPointParam = s.nearestPoint(p); const QPointF nearestPoint = s.pointAt(nearestPointParam); const qreal distance = kisDistance(p, nearestPoint); // are we within the allowed distance ? if (distance > distanceThreshold) continue; // are we closer to the last closest point ? if (distance < minDistance) { segment->path = shape; segment->segmentStart = s.first(); segment->positionOnSegment = nearestPointParam; } } } if (!segment->isValid()) { segment.reset(); } return segment.take(); } void KoPathTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); Q_D(KoToolBase); m_activatedTemporarily = activation == TemporaryActivation; // retrieve the actual global handle radius m_handleRadius = handleRadius(); d->canvas->snapGuide()->reset(); useCursor(m_selectCursor); m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(updateActions())); m_shapeFillResourceConnector.connectToCanvas(d->canvas); initializeWithShapes(shapes.toList()); connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve()), Qt::UniqueConnection); connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine()), Qt::UniqueConnection); connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine()), Qt::UniqueConnection); connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve()), Qt::UniqueConnection); connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints()), Qt::UniqueConnection); connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints()), Qt::UniqueConnection); connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint()), Qt::UniqueConnection); connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment()), Qt::UniqueConnection); connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints()), Qt::UniqueConnection); connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints()), Qt::UniqueConnection); connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath()), Qt::UniqueConnection); connect(m_points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*)), Qt::UniqueConnection); connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged()), Qt::UniqueConnection); } void KoPathTool::slotSelectionChanged() { Q_D(KoToolBase); QList shapes = - d->canvas->selectedShapesProxy()->selection()->selectedEditableShapesAndDelegates(); + d->canvas->selectedShapesProxy()->selection()->selectedEditableShapesAndDelegates(); initializeWithShapes(shapes); } void KoPathTool::notifyPathPointsChanged(KoPathShape *shape) { Q_UNUSED(shape); // active handle and selection might have already become invalid, so just // delete them without dereferencing anything... delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; } void KoPathTool::clearActivePointSelectionReferences() { delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; m_pointSelection.clear(); } void KoPathTool::initializeWithShapes(const QList shapes) { QList selectedShapes; Q_FOREACH (KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape && pathShape->isShapeEditable()) { selectedShapes.append(pathShape); } } const QRectF oldBoundingRect = - KoShape::boundingRect(implicitCastList(m_pointSelection.selectedShapes())); + KoShape::boundingRect(implicitCastList(m_pointSelection.selectedShapes())); if (selectedShapes != m_pointSelection.selectedShapes()) { clearActivePointSelectionReferences(); m_pointSelection.setSelectedShapes(selectedShapes); repaintDecorations(); } Q_FOREACH (KoPathShape *shape, selectedShapes) { // as the tool is just in activation repaintDecorations does not yet get called // so we need to use repaint of the tool and it is only needed to repaint the // current canvas repaint(shape->boundingRect()); } repaint(oldBoundingRect); updateOptionsWidget(); updateActions(); } void KoPathTool::updateOptionsWidget() { PathToolOptionWidget::Types type; QList selectedShapes = m_pointSelection.selectedShapes(); Q_FOREACH (KoPathShape *shape, selectedShapes) { KoParameterShape * parameterShape = dynamic_cast(shape); type |= parameterShape && parameterShape->isParametricShape() ? - PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath; + PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath; } emit singleShapeChanged(selectedShapes.size() == 1 ? selectedShapes.first() : 0); emit typeChanged(type); } void KoPathTool::updateActions() { QList pointData = m_pointSelection.selectedPointsData(); bool canBreakAtPoint = false; bool hasNonSmoothPoints = false; bool hasNonSymmetricPoints = false; bool hasNonSplitPoints = false; bool hasNonLinePoints = false; bool hasNonCurvePoints = false; bool canJoinSubpaths = false; if (!pointData.isEmpty()) { Q_FOREACH (const KoPathPointData &pd, pointData) { const int subpathIndex = pd.pointIndex.first; const int pointIndex = pd.pointIndex.second; canBreakAtPoint |= pd.pathShape->isClosedSubpath(subpathIndex) || - (pointIndex > 0 && pointIndex < pd.pathShape->subpathPointCount(subpathIndex) - 1); + (pointIndex > 0 && pointIndex < pd.pathShape->subpathPointCount(subpathIndex) - 1); KoPathPoint *point = pd.pathShape->pointByIndex(pd.pointIndex); hasNonSmoothPoints |= !(point->properties() & KoPathPoint::IsSmooth); hasNonSymmetricPoints |= !(point->properties() & KoPathPoint::IsSymmetric); hasNonSplitPoints |= - point->properties() & KoPathPoint::IsSymmetric || - point->properties() & KoPathPoint::IsSmooth; + point->properties() & KoPathPoint::IsSymmetric || + point->properties() & KoPathPoint::IsSmooth; hasNonLinePoints |= point->activeControlPoint1() || point->activeControlPoint2(); hasNonCurvePoints |= !point->activeControlPoint1() && !point->activeControlPoint2(); } if (pointData.size() == 2) { const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); canJoinSubpaths = checkCanJoinToPoints(pd1, pd2); } } m_actionPathPointCorner->setEnabled(hasNonSplitPoints); m_actionPathPointSmooth->setEnabled(hasNonSmoothPoints); m_actionPathPointSymmetric->setEnabled(hasNonSymmetricPoints); m_actionRemovePoint->setEnabled(!pointData.isEmpty()); m_actionBreakPoint->setEnabled(canBreakAtPoint); m_actionCurvePoint->setEnabled(hasNonCurvePoints); m_actionLinePoint->setEnabled(hasNonLinePoints); m_actionJoinSegment->setEnabled(canJoinSubpaths); m_actionMergePoints->setEnabled(canJoinSubpaths); QList segments(m_pointSelection.selectedSegmentsData()); bool canSplitAtSegment = false; bool canConvertSegmentToLine = false; bool canConvertSegmentToCurve= false; if (!segments.isEmpty()) { canSplitAtSegment = segments.size() == 1; bool hasLines = false; bool hasCurves = false; Q_FOREACH (const KoPathPointData &pd, segments) { KoPathSegment segment = pd.pathShape->segmentByIndex(pd.pointIndex); hasLines |= segment.degree() == 1; hasCurves |= segment.degree() > 1; } canConvertSegmentToLine = !segments.isEmpty() && hasCurves; canConvertSegmentToCurve= !segments.isEmpty() && hasLines; } m_actionAddPoint->setEnabled(canSplitAtSegment); m_actionLineSegment->setEnabled(canConvertSegmentToLine); m_actionCurveSegment->setEnabled(canConvertSegmentToCurve); m_actionBreakSegment->setEnabled(canSplitAtSegment); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); bool haveConvertibleShapes = false; Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); KoSvgTextShape *textShape = dynamic_cast(shape); if (textShape || - (parameterShape && parameterShape->isParametricShape())) { + (parameterShape && parameterShape->isParametricShape())) { haveConvertibleShapes = true; break; } } m_actionConvertToPath->setEnabled(haveConvertibleShapes); } void KoPathTool::deactivate() { Q_D(KoToolBase); m_shapeFillResourceConnector.disconnect(); m_canvasConnections.clear(); m_pointSelection.clear(); m_pointSelection.setSelectedShapes(QList()); delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; delete m_currentStrategy; m_currentStrategy = 0; d->canvas->snapGuide()->reset(); disconnect(m_actionCurvePoint, 0, this, 0); disconnect(m_actionLinePoint, 0, this, 0); disconnect(m_actionLineSegment, 0, this, 0); disconnect(m_actionCurveSegment, 0, this, 0); disconnect(m_actionAddPoint, 0, this, 0); disconnect(m_actionRemovePoint, 0, this, 0); disconnect(m_actionBreakPoint, 0, this, 0); disconnect(m_actionBreakSegment, 0, this, 0); disconnect(m_actionJoinSegment, 0, this, 0); disconnect(m_actionMergePoints, 0, this, 0); disconnect(m_actionConvertToPath, 0, this, 0); disconnect(m_points, 0, this, 0); disconnect(&m_pointSelection, 0, this, 0); KoToolBase::deactivate(); } void KoPathTool::documentResourceChanged(int key, const QVariant & res) { if (key == KoDocumentResourceManager::HandleRadius) { int oldHandleRadius = m_handleRadius; m_handleRadius = res.toUInt(); // repaint with the bigger of old and new handle radius int maxRadius = qMax(m_handleRadius, oldHandleRadius); Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect(); repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius)); } } } void KoPathTool::pointSelectionChanged() { Q_D(KoToolBase); updateActions(); d->canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList()); emit selectionChanged(m_pointSelection.hasSelection()); } void KoPathTool::repaint(const QRectF &repaintRect) { Q_D(KoToolBase); //debugFlake <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius; // widen border to take antialiasing into account qreal radius = m_handleRadius + 1; d->canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } namespace { void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2) { if (a1->isEnabled() || a2->isEnabled()) { menu->addAction(a1); menu->addAction(a2); menu->addSeparator(); } } void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2, QAction *a3) { if (a1->isEnabled() || a2->isEnabled()) { menu->addAction(a1); menu->addAction(a2); menu->addAction(a3); menu->addSeparator(); } } } QMenu *KoPathTool::popupActionsMenu() { if (m_activeHandle) { m_activeHandle->trySelectHandle(); } if (m_activeSegment && m_activeSegment->isValid()) { KoPathShape *shape = m_activeSegment->path; KoPathSegment segment = shape->segmentByIndex(shape->pathPointIndex(m_activeSegment->segmentStart)); m_pointSelection.add(segment.first(), true); m_pointSelection.add(segment.second(), false); } if (m_contextMenu) { m_contextMenu->clear(); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionPathPointCorner, m_actionPathPointSmooth, m_actionPathPointSymmetric); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionCurvePoint, m_actionLinePoint); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionAddPoint, m_actionRemovePoint); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionLineSegment, m_actionCurveSegment); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionBreakPoint, m_actionBreakSegment); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionJoinSegment, m_actionMergePoints); m_contextMenu->addAction(m_actionConvertToPath); m_contextMenu->addSeparator(); } return m_contextMenu.data(); } void KoPathTool::deleteSelection() { removePoints(); } KoToolSelection * KoPathTool::selection() { return &m_pointSelection; } void KoPathTool::requestUndoDuringStroke() { // noop! } void KoPathTool::requestStrokeCancellation() { explicitUserStrokeEndRequest(); } void KoPathTool::requestStrokeEnd() { // noop! } void KoPathTool::explicitUserStrokeEndRequest() { if (m_activatedTemporarily) { emit done(); } } diff --git a/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h b/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h index 51d5d9cfa6..894781692b 100644 --- a/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h +++ b/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h @@ -1,125 +1,125 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOMPOSITEOPALPHADARKEN_H_ #define KOCOMPOSITEOPALPHADARKEN_H_ #include "KoCompositeOpFunctions.h" #include "KoCompositeOpBase.h" #include /** * A template version of the alphadarken composite operation to use in colorspaces */ template class KoCompositeOpAlphaDarken: public KoCompositeOp { typedef typename Traits::channels_type channels_type; static const qint32 channels_nb = Traits::channels_nb; static const qint32 alpha_pos = Traits::alpha_pos; public: KoCompositeOpAlphaDarken(const KoColorSpace* cs): KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, i18n("Alpha darken"), KoCompositeOp::categoryMix()) { } using KoCompositeOp::composite; void composite(const KoCompositeOp::ParameterInfo& params) const override { if(params.maskRowStart != 0) genericComposite(params); else genericComposite(params); } template void genericComposite(const KoCompositeOp::ParameterInfo& params) const { using namespace Arithmetic; qint32 srcInc = (params.srcRowStride == 0) ? 0 : channels_nb; channels_type flow = scale(params.flow); - channels_type opacity = scale(params.opacity); + channels_type opacity = mul(flow, scale(params.opacity)); quint8* dstRowStart = params.dstRowStart; const quint8* srcRowStart = params.srcRowStart; const quint8* maskRowStart = params.maskRowStart; for(quint32 r=params.rows; r>0; --r) { const channels_type* src = reinterpret_cast(srcRowStart); channels_type* dst = reinterpret_cast(dstRowStart); const quint8* mask = maskRowStart; for(qint32 c=params.cols; c>0; --c) { channels_type srcAlpha = (alpha_pos == -1) ? unitValue() : src[alpha_pos]; channels_type dstAlpha = (alpha_pos == -1) ? unitValue() : dst[alpha_pos]; channels_type mskAlpha = useMask ? mul(scale(*mask), srcAlpha) : srcAlpha; srcAlpha = mul(mskAlpha, opacity); if(dstAlpha != zeroValue()) { for(qint32 i=0; i (*params.lastOpacity); + channels_type averageOpacity = mul(flow, scale(*params.lastOpacity)); if (averageOpacity > opacity) { channels_type reverseBlend = KoColorSpaceMaths::divide(dstAlpha, averageOpacity); fullFlowAlpha = averageOpacity > dstAlpha ? lerp(srcAlpha, averageOpacity, reverseBlend) : dstAlpha; } else { fullFlowAlpha = opacity > dstAlpha ? lerp(dstAlpha, opacity, mskAlpha) : dstAlpha; } if (params.flow == 1.0) { dstAlpha = fullFlowAlpha; } else { - channels_type zeroFlowAlpha = dstAlpha; + channels_type zeroFlowAlpha = unionShapeOpacity(srcAlpha, dstAlpha); dstAlpha = lerp(zeroFlowAlpha, fullFlowAlpha, flow); } dst[alpha_pos] = dstAlpha; } src += srcInc; dst += channels_nb; if(useMask) ++mask; } srcRowStart += params.srcRowStride; dstRowStart += params.dstRowStride; maskRowStart += params.maskRowStride; } } }; #endif // KOCOMPOSITEOPALPHADARKEN_H_ diff --git a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h index 367357d927..4c020aba19 100644 --- a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h +++ b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h @@ -1,218 +1,228 @@ /* * Copyright (c) 2016 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOOPTIMIZEDCOMPOSITEOPALPHADARKEN128_H #define KOOPTIMIZEDCOMPOSITEOPALPHADARKEN128_H #include "KoCompositeOpBase.h" #include "KoCompositeOpRegistry.h" #include "KoStreamedMath.h" template struct AlphaDarkenCompositor128 { struct OptionalParams { OptionalParams(const KoCompositeOp::ParameterInfo& params) : flow(params.flow) - , averageOpacity(*params.lastOpacity) + , averageOpacity(*params.lastOpacity * params.flow) + , premultipliedOpacity(params.opacity * params.flow) { } float flow; float averageOpacity; + float premultipliedOpacity; }; struct Pixel { channels_type red; channels_type green; channels_type blue; channels_type alpha; }; /** * This is a vector equivalent of compositeOnePixelScalar(). It is considered * to process Vc::float_v::size() pixels in a single pass. * * o the \p haveMask parameter points whether the real (non-null) mask * pointer is passed to the function. * o the \p src pointer may be aligned to vector boundary or may be * not. In case not, it must be pointed with a special parameter * \p src_aligned. * o the \p dst pointer must always(!) be aligned to the boundary * of a streaming vector. Unaligned writes are really expensive. * o This function is *never* used if HAVE_VC is not present */ template static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { const Pixel *sp = reinterpret_cast(src); Pixel *dp = reinterpret_cast(dst); Vc::float_v src_c1; Vc::float_v src_c2; Vc::float_v src_c3; Vc::float_v src_alpha; const Vc::float_v::IndexType indexes(Vc::IndexesFromZero); Vc::InterleavedMemoryWrapper data(const_cast(sp)); tie(src_c1, src_c2, src_c3, src_alpha) = data[indexes]; Vc::float_v msk_norm_alpha; if (haveMask) { const Vc::float_v uint8Rec1((float)1.0 / 255.0); Vc::float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask); msk_norm_alpha = mask_vec * uint8Rec1 * src_alpha; } else { msk_norm_alpha = src_alpha; } - Vc::float_v opacity_vec(opacity); + // we don't use directly passed value + Q_UNUSED(opacity); + + // instead we should use opacity premultiplied by flow + opacity = oparams.premultipliedOpacity; + Vc::float_v opacity_vec(oparams.premultipliedOpacity); src_alpha = msk_norm_alpha * opacity_vec; const Vc::float_v zeroValue(KoColorSpaceMathsTraits::zeroValue); Vc::float_v dst_c1; Vc::float_v dst_c2; Vc::float_v dst_c3; Vc::float_v dst_alpha; Vc::InterleavedMemoryWrapper dataDest(dp); tie(dst_c1, dst_c2, dst_c3, dst_alpha) = dataDest[indexes]; Vc::float_m empty_dst_pixels_mask = dst_alpha == zeroValue; if (!empty_dst_pixels_mask.isFull()) { if (empty_dst_pixels_mask.isEmpty()) { dst_c1 = (src_c1 - dst_c1) * src_alpha + dst_c1; dst_c2 = (src_c2 - dst_c2) * src_alpha + dst_c2; dst_c3 = (src_c3 - dst_c3) * src_alpha + dst_c3; } else { dst_c1(empty_dst_pixels_mask) = src_c1; dst_c2(empty_dst_pixels_mask) = src_c2; dst_c3(empty_dst_pixels_mask) = src_c3; Vc::float_m not_empty_dst_pixels_mask = !empty_dst_pixels_mask; dst_c1(not_empty_dst_pixels_mask) = (src_c1 - dst_c1) * src_alpha + dst_c1; dst_c2(not_empty_dst_pixels_mask) = (src_c2 - dst_c2) * src_alpha + dst_c2; dst_c3(not_empty_dst_pixels_mask) = (src_c3 - dst_c3) * src_alpha + dst_c3; } } else { dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } Vc::float_v fullFlowAlpha(dst_alpha); if (oparams.averageOpacity > opacity) { Vc::float_v average_opacity_vec(oparams.averageOpacity); Vc::float_m fullFlowAlpha_mask = average_opacity_vec > dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = (average_opacity_vec - src_alpha) * (dst_alpha / average_opacity_vec) + src_alpha; } else { Vc::float_m fullFlowAlpha_mask = opacity_vec > dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha; } if (oparams.flow == 1.0) { dst_alpha = fullFlowAlpha; } else { - Vc::float_v zeroFlowAlpha = dst_alpha; + Vc::float_v zeroFlowAlpha = src_alpha + dst_alpha - src_alpha * dst_alpha; Vc::float_v flow_norm_vec(oparams.flow); dst_alpha = (fullFlowAlpha - zeroFlowAlpha) * flow_norm_vec + zeroFlowAlpha; } dataDest[indexes] = tie(dst_c1, dst_c2, dst_c3, dst_alpha); } /** * Composes one pixel of the source into the destination */ template static ALWAYS_INLINE void compositeOnePixelScalar(const quint8 *s, quint8 *d, const quint8 *mask, float opacity, const OptionalParams &oparams) { using namespace Arithmetic; const qint32 alpha_pos = 3; const channels_type *src = reinterpret_cast(s); channels_type *dst = reinterpret_cast(d); float dstAlphaNorm = dst[alpha_pos]; const float uint8Rec1 = 1.0 / 255.0; float mskAlphaNorm = haveMask ? float(*mask) * uint8Rec1 * src[alpha_pos] : src[alpha_pos]; + Q_UNUSED(opacity); + opacity = oparams.premultipliedOpacity; + float srcAlphaNorm = mskAlphaNorm * opacity; if (dstAlphaNorm != 0) { dst[0] = lerp(dst[0], src[0], srcAlphaNorm); dst[1] = lerp(dst[1], src[1], srcAlphaNorm); dst[2] = lerp(dst[2], src[2], srcAlphaNorm); } else { const pixel_type *s = reinterpret_cast(src); pixel_type *d = reinterpret_cast(dst); *d = *s; } float flow = oparams.flow; float averageOpacity = oparams.averageOpacity; float fullFlowAlpha; if (averageOpacity > opacity) { fullFlowAlpha = averageOpacity > dstAlphaNorm ? lerp(srcAlphaNorm, averageOpacity, dstAlphaNorm / averageOpacity) : dstAlphaNorm; } else { fullFlowAlpha = opacity > dstAlphaNorm ? lerp(dstAlphaNorm, opacity, mskAlphaNorm) : dstAlphaNorm; } if (flow == 1.0) { dst[alpha_pos] = fullFlowAlpha; } else { - float zeroFlowAlpha = dstAlphaNorm; + float zeroFlowAlpha = unionShapeOpacity(srcAlphaNorm, dstAlphaNorm); dst[alpha_pos] = lerp(zeroFlowAlpha, fullFlowAlpha, flow); } } }; /** * An optimized version of a composite op for the use in 16 byte * colorspaces with alpha channel placed at the last byte of * the pixel: C1_C2_C3_A. */ template class KoOptimizedCompositeOpAlphaDarken128 : public KoCompositeOp { public: KoOptimizedCompositeOpAlphaDarken128(const KoColorSpace* cs) : KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, i18n("Alpha darken"), KoCompositeOp::categoryMix()) {} using KoCompositeOp::composite; virtual void composite(const KoCompositeOp::ParameterInfo& params) const { if(params.maskRowStart) { KoStreamedMath<_impl>::template genericComposite128 >(params); } else { KoStreamedMath<_impl>::template genericComposite128 >(params); } } }; #endif // KOOPTIMIZEDCOMPOSITEOPALPHADARKEN128_H diff --git a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h index 6932d1aac3..41de2b363d 100644 --- a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h +++ b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h @@ -1,258 +1,269 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_ #define KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_ #include "KoCompositeOpBase.h" #include "KoCompositeOpRegistry.h" #include #include "KoStreamedMath.h" template struct AlphaDarkenCompositor32 { struct OptionalParams { OptionalParams(const KoCompositeOp::ParameterInfo& params) : flow(params.flow), - averageOpacity(*params.lastOpacity) + averageOpacity(*params.lastOpacity * params.flow), + premultipliedOpacity(params.opacity * params.flow) { } float flow; float averageOpacity; + float premultipliedOpacity; }; /** * This is a vector equivalent of compositeOnePixelScalar(). It is considered * to process Vc::float_v::size() pixels in a single pass. * * o the \p haveMask parameter points whether the real (non-null) mask * pointer is passed to the function. * o the \p src pointer may be aligned to vector boundary or may be * not. In case not, it must be pointed with a special parameter * \p src_aligned. * o the \p dst pointer must always(!) be aligned to the boundary * of a streaming vector. Unaligned writes are really expensive. * o This function is *never* used if HAVE_VC is not present */ template static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { Vc::float_v src_alpha; Vc::float_v dst_alpha; - Vc::float_v opacity_vec(255.0 * opacity); + // we don't use directly passed value + Q_UNUSED(opacity); + + // instead we should use opacity premultiplied by flow + opacity = oparams.premultipliedOpacity; + Vc::float_v opacity_vec(255.0 * oparams.premultipliedOpacity); Vc::float_v average_opacity_vec(255.0 * oparams.averageOpacity); Vc::float_v flow_norm_vec(oparams.flow); Vc::float_v uint8MaxRec2((float)1.0 / (255.0 * 255.0)); Vc::float_v uint8MaxRec1((float)1.0 / 255.0); Vc::float_v uint8Max((float)255.0); Vc::float_v zeroValue(Vc::Zero); Vc::float_v msk_norm_alpha; src_alpha = KoStreamedMath<_impl>::template fetch_alpha_32(src); if (haveMask) { Vc::float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask); msk_norm_alpha = src_alpha * mask_vec * uint8MaxRec2; } else { msk_norm_alpha = src_alpha * uint8MaxRec1; } dst_alpha = KoStreamedMath<_impl>::template fetch_alpha_32(dst); src_alpha = msk_norm_alpha * opacity_vec; Vc::float_m empty_dst_pixels_mask = dst_alpha == zeroValue; Vc::float_v src_c1; Vc::float_v src_c2; Vc::float_v src_c3; Vc::float_v dst_c1; Vc::float_v dst_c2; Vc::float_v dst_c3; KoStreamedMath<_impl>::template fetch_colors_32(src, src_c1, src_c2, src_c3); bool srcAlphaIsZero = (src_alpha == zeroValue).isFull(); if (srcAlphaIsZero) return; bool dstAlphaIsZero = empty_dst_pixels_mask.isFull(); Vc::float_v dst_blend = src_alpha * uint8MaxRec1; bool srcAlphaIsUnit = (src_alpha == uint8Max).isFull(); if (dstAlphaIsZero) { dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } else if (srcAlphaIsUnit) { bool dstAlphaIsUnit = (dst_alpha == uint8Max).isFull(); if (dstAlphaIsUnit) { memcpy(dst, src, 4 * Vc::float_v::size()); return; } else { dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } } else if (empty_dst_pixels_mask.isEmpty()) { KoStreamedMath<_impl>::template fetch_colors_32(dst, dst_c1, dst_c2, dst_c3); dst_c1 = dst_blend * (src_c1 - dst_c1) + dst_c1; dst_c2 = dst_blend * (src_c2 - dst_c2) + dst_c2; dst_c3 = dst_blend * (src_c3 - dst_c3) + dst_c3; } else { KoStreamedMath<_impl>::template fetch_colors_32(dst, dst_c1, dst_c2, dst_c3); dst_c1(empty_dst_pixels_mask) = src_c1; dst_c2(empty_dst_pixels_mask) = src_c2; dst_c3(empty_dst_pixels_mask) = src_c3; Vc::float_m not_empty_dst_pixels_mask = !empty_dst_pixels_mask; dst_c1(not_empty_dst_pixels_mask) = dst_blend * (src_c1 - dst_c1) + dst_c1; dst_c2(not_empty_dst_pixels_mask) = dst_blend * (src_c2 - dst_c2) + dst_c2; dst_c3(not_empty_dst_pixels_mask) = dst_blend * (src_c3 - dst_c3) + dst_c3; } Vc::float_v fullFlowAlpha; if (oparams.averageOpacity > opacity) { Vc::float_m fullFlowAlpha_mask = average_opacity_vec > dst_alpha; if (fullFlowAlpha_mask.isEmpty()) { fullFlowAlpha = dst_alpha; } else { Vc::float_v reverse_blend = dst_alpha / average_opacity_vec; Vc::float_v opt1 = (average_opacity_vec - src_alpha) * reverse_blend + src_alpha; fullFlowAlpha(!fullFlowAlpha_mask) = dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = opt1; } } else { Vc::float_m fullFlowAlpha_mask = opacity_vec > dst_alpha; if (fullFlowAlpha_mask.isEmpty()) { fullFlowAlpha = dst_alpha; } else { Vc::float_v opt1 = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha; fullFlowAlpha(!fullFlowAlpha_mask) = dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = opt1; } } if (oparams.flow == 1.0) { dst_alpha = fullFlowAlpha; } else { - Vc::float_v zeroFlowAlpha = dst_alpha; + Vc::float_v zeroFlowAlpha = src_alpha + dst_alpha - + dst_blend * dst_alpha; dst_alpha = (fullFlowAlpha - zeroFlowAlpha) * flow_norm_vec + zeroFlowAlpha; } KoStreamedMath<_impl>::write_channels_32(dst, dst_alpha, dst_c1, dst_c2, dst_c3); } /** * Composes one pixel of the source into the destination */ template static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { using namespace Arithmetic; const qint32 alpha_pos = 3; const float uint8Rec1 = 1.0 / 255.0; const float uint8Rec2 = 1.0 / (255.0 * 255.0); const float uint8Max = 255.0; quint8 dstAlphaInt = dst[alpha_pos]; float dstAlphaNorm = dstAlphaInt ? dstAlphaInt * uint8Rec1 : 0.0; float srcAlphaNorm; float mskAlphaNorm; + Q_UNUSED(opacity); + opacity = oparams.premultipliedOpacity; + if (haveMask) { mskAlphaNorm = float(*mask) * uint8Rec2 * src[alpha_pos]; srcAlphaNorm = mskAlphaNorm * opacity; } else { mskAlphaNorm = src[alpha_pos] * uint8Rec1; srcAlphaNorm = mskAlphaNorm * opacity; } if (dstAlphaInt != 0) { dst[0] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[0], src[0], srcAlphaNorm); dst[1] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[1], src[1], srcAlphaNorm); dst[2] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[2], src[2], srcAlphaNorm); } else { const pixel_type *s = reinterpret_cast(src); pixel_type *d = reinterpret_cast(dst); *d = *s; } float flow = oparams.flow; float averageOpacity = oparams.averageOpacity; float fullFlowAlpha; if (averageOpacity > opacity) { fullFlowAlpha = averageOpacity > dstAlphaNorm ? lerp(srcAlphaNorm, averageOpacity, dstAlphaNorm / averageOpacity) : dstAlphaNorm; } else { fullFlowAlpha = opacity > dstAlphaNorm ? lerp(dstAlphaNorm, opacity, mskAlphaNorm) : dstAlphaNorm; } float dstAlpha; if (flow == 1.0) { dstAlpha = fullFlowAlpha * uint8Max; } else { - float zeroFlowAlpha = dstAlphaNorm; + float zeroFlowAlpha = unionShapeOpacity(srcAlphaNorm, dstAlphaNorm); dstAlpha = lerp(zeroFlowAlpha, fullFlowAlpha, flow) * uint8Max; } dst[alpha_pos] = KoStreamedMath<_impl>::round_float_to_uint(dstAlpha); } }; /** * An optimized version of a composite op for the use in 4 byte * colorspaces with alpha channel placed at the last byte of * the pixel: C1_C2_C3_A. */ template class KoOptimizedCompositeOpAlphaDarken32 : public KoCompositeOp { public: KoOptimizedCompositeOpAlphaDarken32(const KoColorSpace* cs) : KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, i18n("Alpha darken"), KoCompositeOp::categoryMix()) {} using KoCompositeOp::composite; virtual void composite(const KoCompositeOp::ParameterInfo& params) const { if(params.maskRowStart) { KoStreamedMath<_impl>::template genericComposite32 >(params); } else { KoStreamedMath<_impl>::template genericComposite32 >(params); } } }; #endif // KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_ diff --git a/libs/store/KoQuaZipStore.cpp b/libs/store/KoQuaZipStore.cpp index a48e9d8187..bc0461498a 100644 --- a/libs/store/KoQuaZipStore.cpp +++ b/libs/store/KoQuaZipStore.cpp @@ -1,260 +1,273 @@ /* * Copyright (C) 2019 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 "KoQuaZipStore.h" #include "KoStore_p.h" #include #include #include #include #include #include #include #include #include #include #include #include struct KoQuaZipStore::Private { Private() {} ~Private() {} QuaZip *archive {0}; QuaZipFile *currentFile {0}; int compressionLevel {Z_DEFAULT_COMPRESSION}; bool usingSaveFile {false}; }; KoQuaZipStore::KoQuaZipStore(const QString &_filename, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype) : KoStore(_mode, writeMimetype) , dd(new Private()) { Q_D(KoStore); + debugStore << "KoQuaZipStore" << _filename; d->localFileName = _filename; dd->archive = new QuaZip(_filename); init(appIdentification); } KoQuaZipStore::KoQuaZipStore(QIODevice *dev, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype) : KoStore(_mode, writeMimetype) , dd(new Private()) { Q_D(KoStore); + debugStore << "KoQuaZipStore" << dev; dd->archive = new QuaZip(dev); init(appIdentification); } KoQuaZipStore::~KoQuaZipStore() { Q_D(KoStore); if (dd->currentFile && dd->currentFile->isOpen()) { dd->currentFile->close(); } if (!d->finalized) { finalize(); } delete dd->archive; delete dd->currentFile; } void KoQuaZipStore::setCompressionEnabled(bool enabled) { if (enabled) { dd->compressionLevel = Z_BEST_COMPRESSION; } else { dd->compressionLevel = Z_NO_COMPRESSION; } } qint64 KoQuaZipStore::write(const char *_data, qint64 _len) { Q_D(KoStore); if (_len == 0) return 0; if (!d->isOpen) { errorStore << "KoStore: You must open before writing" << endl; return 0; } if (d->mode != Write) { errorStore << "KoStore: Can not write to store that is opened for reading" << endl; return 0; } d->size += _len; if (dd->currentFile->write(_data, _len)) { // writeData returns a bool! return _len; } return 0; } QStringList KoQuaZipStore::directoryList() const { + debugStore << dd->archive->getFileNameList(); return dd->archive->getFileNameList(); } void KoQuaZipStore::init(const QByteArray &appIdentification) { Q_D(KoStore); bool enableZip64 = false; if (appIdentification == "application/x-krita") { enableZip64 = KSharedConfig::openConfig()->group("").readEntry("UseZip64", false); } dd->archive->setZip64Enabled(enableZip64); dd->archive->setFileNameCodec("UTF-8"); dd->usingSaveFile = dd->archive->getIoDevice() && dd->archive->getIoDevice()->inherits("QSaveFile"); dd->archive->setAutoClose(!dd->usingSaveFile); d->good = dd->archive->open(d->mode == Write ? QuaZip::mdCreate : QuaZip::mdUnzip); if (!d->good) { return; } if (d->mode == Write) { if (d->writeMimetype) { QuaZipFile f(dd->archive); QuaZipNewInfo newInfo("mimetype"); newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther); if (!f.open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, Z_NO_COMPRESSION)) { d->good = false; return; } f.write(appIdentification); f.close(); } } else { + debugStore << dd->archive->getEntriesCount() << dd->archive->getFileNameList(); d->good = dd->archive->getEntriesCount(); } } bool KoQuaZipStore::doFinalize() { Q_D(KoStore); d->stream = 0; if (!dd->usingSaveFile) { dd->archive->close(); } return dd->archive->getZipError() == ZIP_OK; } bool KoQuaZipStore::openWrite(const QString &name) { Q_D(KoStore); QString fixedPath = name; fixedPath.replace("//", "/"); delete d->stream; d->stream = 0; // Not used when writing delete dd->currentFile; dd->currentFile = new QuaZipFile(dd->archive); QuaZipNewInfo newInfo(fixedPath); newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther); bool r = dd->currentFile->open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, dd->compressionLevel); if (!r) { qWarning() << "Could not open" << name << dd->currentFile->getZipError(); } return r; } bool KoQuaZipStore::openRead(const QString &name) { Q_D(KoStore); + QString fixedPath = name; fixedPath.replace("//", "/"); delete d->stream; d->stream = 0; delete dd->currentFile; dd->currentFile = 0; if (!currentPath().isEmpty() && !fixedPath.startsWith(currentPath())) { fixedPath = currentPath() + '/' + fixedPath; } + debugStore << "openRead" << name << fixedPath << currentPath(); + if (!dd->archive->setCurrentFile(fixedPath)) { - warnStore << "\t\tCould not set current file" << dd->archive->getZipError(); + qWarning() << "\t\tCould not set current file" << dd->archive->getZipError(); return false; } dd->currentFile = new QuaZipFile(dd->archive); if (!dd->currentFile->open(QIODevice::ReadOnly)) { - warnStore << "\t\t\tBut could not open!!!" << dd->archive->getZipError(); + qWarning() << "\t\t\tBut could not open!!!" << dd->archive->getZipError(); return false; } d->stream = dd->currentFile; d->size = dd->currentFile->size(); return true; } bool KoQuaZipStore::closeWrite() { Q_D(KoStore); dd->currentFile->close(); d->stream = 0; return dd->currentFile->getZipError() == ZIP_OK; } bool KoQuaZipStore::closeRead() { Q_D(KoStore); d->stream = 0; return true; } -bool KoQuaZipStore::enterRelativeDirectory(const QString &) +bool KoQuaZipStore::enterRelativeDirectory(const QString &path) { + debugStore << "enterRelativeDirectory()" << path; return true; } bool KoQuaZipStore::enterAbsoluteDirectory(const QString &path) { + debugStore << "enterAbsoluteDirectory()" << path; + QString fixedPath = path; fixedPath.replace("//", "/"); if (fixedPath.isEmpty()) { - return true; + fixedPath = "/"; } QuaZipDir currentDir (dd->archive, fixedPath); return currentDir.exists(); } bool KoQuaZipStore::fileExists(const QString &absPath) const { QString fixedPath = absPath; fixedPath.replace("//", "/"); + + debugStore << "fileExists()" << fixedPath << dd->archive->getFileNameList().contains(fixedPath); + return dd->archive->getFileNameList().contains(fixedPath); } diff --git a/libs/store/KoStore.cpp b/libs/store/KoStore.cpp index 9a4287b4cd..d07de3696f 100644 --- a/libs/store/KoStore.cpp +++ b/libs/store/KoStore.cpp @@ -1,438 +1,440 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2002 David Faure , Werner Trobin Copyright (C) 2010 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 "KoStore.h" #include "KoStore_p.h" #include "KoQuaZipStore.h" #include "KoDirectoryStore.h" #include #include #include #include #include #define DefaultFormat KoStore::Zip static KoStore::Backend determineBackend(QIODevice *dev) { unsigned char buf[5]; if (dev->read((char *)buf, 4) < 4) return DefaultFormat; // will create a "bad" store (bad()==true) if (buf[0] == 'P' && buf[1] == 'K' && buf[2] == 3 && buf[3] == 4) return KoStore::Zip; return DefaultFormat; // fallback } KoStore* KoStore::createStore(const QString& fileName, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype) { if (backend == Auto) { if (mode == KoStore::Write) backend = DefaultFormat; else { QFileInfo inf(fileName); if (inf.isDir()) backend = Directory; else { QFile file(fileName); if (file.open(QIODevice::ReadOnly)) backend = determineBackend(&file); else backend = DefaultFormat; // will create a "bad" store (bad()==true) } } } switch (backend) { case Zip: return new KoQuaZipStore(fileName, mode, appIdentification, writeMimetype); case Directory: return new KoDirectoryStore(fileName /* should be a dir name.... */, mode, writeMimetype); default: warnStore << "Unsupported backend requested for KoStore : " << backend; return 0; } } KoStore* KoStore::createStore(QIODevice *device, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype) { if (backend == Auto) { if (mode == KoStore::Write) backend = DefaultFormat; else { if (device->open(QIODevice::ReadOnly)) { backend = determineBackend(device); device->close(); } } } switch (backend) { case Directory: errorStore << "Can't create a Directory store for a memory buffer!" << endl; return 0; case Zip: return new KoQuaZipStore(device, mode, appIdentification, writeMimetype); default: warnStore << "Unsupported backend requested for KoStore : " << backend; return 0; } } namespace { const char ROOTPART[] = "root"; const char MAINNAME[] = "maindoc.xml"; } KoStore::KoStore(Mode mode, bool writeMimetype) : d_ptr(new KoStorePrivate(this, mode, writeMimetype)) {} KoStore::~KoStore() { Q_D(KoStore); delete d->stream; delete d_ptr; } bool KoStore::open(const QString & _name) { Q_D(KoStore); // This also converts from relative to absolute, i.e. merges the currentPath() d->fileName = d->toExternalNaming(_name); + debugStore << "KOStore" << _name << d->fileName; + if (d->isOpen) { warnStore << "Store is already opened, missing close"; return false; } if (d->fileName.length() > 512) { errorStore << "KoStore: Filename " << d->fileName << " is too long" << endl; return false; } if (d->mode == Write) { debugStore << "opening for writing" << d->fileName; if (d->filesList.contains(d->fileName)) { warnStore << "KoStore: Duplicate filename" << d->fileName; return false; } d->filesList.append(d->fileName); d->size = 0; if (!openWrite(d->fileName)) return false; } else if (d->mode == Read) { debugStore << "Opening for reading" << d->fileName; if (!openRead(d->fileName)) return false; } else return false; d->isOpen = true; return true; } bool KoStore::isOpen() const { Q_D(const KoStore); return d->isOpen; } bool KoStore::close() { Q_D(KoStore); if (!d->isOpen) { warnStore << "You must open before closing"; return false; } bool ret = d->mode == Write ? closeWrite() : closeRead(); delete d->stream; d->stream = 0; d->isOpen = false; return ret; } QIODevice* KoStore::device() const { Q_D(const KoStore); if (!d->isOpen) warnStore << "You must open before asking for a device"; if (d->mode != Read) warnStore << "Can not get device from store that is opened for writing"; return d->stream; } QByteArray KoStore::read(qint64 max) { Q_D(KoStore); QByteArray data; if (!d->isOpen) { warnStore << "You must open before reading"; return data; } if (d->mode != Read) { errorStore << "KoStore: Can not read from store that is opened for writing" << endl; return data; } return d->stream->read(max); } qint64 KoStore::write(const QByteArray& data) { return write(data.constData(), data.size()); // see below } qint64 KoStore::read(char *_buffer, qint64 _len) { Q_D(KoStore); if (!d->isOpen) { errorStore << "KoStore: You must open before reading" << endl; return -1; } if (d->mode != Read) { errorStore << "KoStore: Can not read from store that is opened for writing" << endl; return -1; } return d->stream->read(_buffer, _len); } qint64 KoStore::write(const char* _data, qint64 _len) { Q_D(KoStore); if (_len == 0) return 0; if (!d->isOpen) { errorStore << "KoStore: You must open before writing" << endl; return 0; } if (d->mode != Write) { errorStore << "KoStore: Can not write to store that is opened for reading" << endl; return 0; } int nwritten = d->stream->write(_data, _len); Q_ASSERT(nwritten == (int)_len); d->size += nwritten; return nwritten; } qint64 KoStore::size() const { Q_D(const KoStore); if (!d->isOpen) { warnStore << "You must open before asking for a size"; return static_cast(-1); } if (d->mode != Read) { warnStore << "Can not get size from store that is opened for writing"; return static_cast(-1); } return d->size; } bool KoStore::enterDirectory(const QString &directory) { Q_D(KoStore); //debugStore <<"enterDirectory" << directory; int pos; bool success = true; QString tmp(directory); while ((pos = tmp.indexOf('/')) != -1 && (success = d->enterDirectoryInternal(tmp.left(pos)))) tmp.remove(0, pos + 1); if (success && !tmp.isEmpty()) return d->enterDirectoryInternal(tmp); return success; } bool KoStore::leaveDirectory() { Q_D(KoStore); if (d->currentPath.isEmpty()) return false; d->currentPath.pop_back(); return enterAbsoluteDirectory(currentPath()); } QString KoStore::currentPath() const { Q_D(const KoStore); QString path; QStringList::ConstIterator it = d->currentPath.begin(); QStringList::ConstIterator end = d->currentPath.end(); for (; it != end; ++it) { path += *it; path += '/'; } return path; } void KoStore::pushDirectory() { Q_D(KoStore); d->directoryStack.push(currentPath()); } void KoStore::popDirectory() { Q_D(KoStore); d->currentPath.clear(); enterAbsoluteDirectory(QString()); enterDirectory(d->directoryStack.pop()); } bool KoStore::extractFile(const QString &srcName, QByteArray &data) { Q_D(KoStore); QBuffer buffer(&data); return d->extractFile(srcName, buffer); } bool KoStorePrivate::extractFile(const QString &srcName, QIODevice &buffer) { if (!q->open(srcName)) return false; if (!buffer.open(QIODevice::WriteOnly)) { q->close(); return false; } QByteArray data; data.resize(8 * 1024); uint total = 0; for (int block = 0; (block = q->read(data.data(), data.size())) > 0; total += block) { buffer.write(data.data(), block); } if (q->size() != static_cast(-1)) Q_ASSERT(total == q->size()); buffer.close(); q->close(); return true; } bool KoStore::seek(qint64 pos) { Q_D(KoStore); return d->stream->seek(pos); } qint64 KoStore::pos() const { Q_D(const KoStore); return d->stream->pos(); } bool KoStore::atEnd() const { Q_D(const KoStore); return d->stream->atEnd(); } // See the specification for details of what this function does. QString KoStorePrivate::toExternalNaming(const QString & _internalNaming) const { if (_internalNaming == ROOTPART) return q->currentPath() + MAINNAME; QString intern; if (_internalNaming.startsWith("tar:/")) // absolute reference intern = _internalNaming.mid(5); // remove protocol else intern = q->currentPath() + _internalNaming; return intern; } bool KoStorePrivate::enterDirectoryInternal(const QString &directory) { if (q->enterRelativeDirectory(directory)) { currentPath.append(directory); return true; } return false; } bool KoStore::hasFile(const QString& fileName) const { Q_D(const KoStore); return fileExists(d->toExternalNaming(fileName)); } bool KoStore::finalize() { Q_D(KoStore); Q_ASSERT(!d->finalized); // call this only once! d->finalized = true; return doFinalize(); } void KoStore::setCompressionEnabled(bool /*e*/) { } bool KoStore::isEncrypted() { return false; } bool KoStore::setPassword(const QString& /*password*/) { return false; } QString KoStore::password() { return QString(); } bool KoStore::bad() const { Q_D(const KoStore); return !d->good; } KoStore::Mode KoStore::mode() const { Q_D(const KoStore); return d->mode; } QStringList KoStore::directoryList() const { return QStringList(); } diff --git a/libs/ui/tool/KisSelectionToolFactoryBase.cpp b/libs/ui/tool/KisSelectionToolFactoryBase.cpp index 2e33e31593..59eda09e53 100644 --- a/libs/ui/tool/KisSelectionToolFactoryBase.cpp +++ b/libs/ui/tool/KisSelectionToolFactoryBase.cpp @@ -1,64 +1,63 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisSelectionToolFactoryBase.h" #include KisSelectionToolFactoryBase::KisSelectionToolFactoryBase(const QString &id) : KisToolPaintFactoryBase(id) { } KisSelectionToolFactoryBase::~KisSelectionToolFactoryBase() { } QList KisSelectionToolFactoryBase::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("selection_tool_mode_add"); actions << actionRegistry->makeQAction("selection_tool_mode_replace"); actions << actionRegistry->makeQAction("selection_tool_mode_subtract"); actions << actionRegistry->makeQAction("selection_tool_mode_intersect"); return actions; } KisToolPolyLineFactoryBase::KisToolPolyLineFactoryBase(const QString &id) - : KisSelectionToolFactoryBase(id) + : KisToolPaintFactoryBase(id) { - } KisToolPolyLineFactoryBase::~KisToolPolyLineFactoryBase() { } QList KisToolPolyLineFactoryBase::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); - QList actions = KisSelectionToolFactoryBase::createActionsImpl(); + QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("undo_polygon_selection"); actions << actionRegistry->makeQAction("selection_tool_mode_add"); return actions; } diff --git a/libs/ui/tool/KisSelectionToolFactoryBase.h b/libs/ui/tool/KisSelectionToolFactoryBase.h index a2e73a0ff9..983f8c7b5a 100644 --- a/libs/ui/tool/KisSelectionToolFactoryBase.h +++ b/libs/ui/tool/KisSelectionToolFactoryBase.h @@ -1,44 +1,44 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSELECTIONTOOLFACTORYBASE_H #define KISSELECTIONTOOLFACTORYBASE_H #include "KisToolPaintFactoryBase.h" #include "kritaui_export.h" class KRITAUI_EXPORT KisSelectionToolFactoryBase : public KisToolPaintFactoryBase { public: explicit KisSelectionToolFactoryBase(const QString &id); ~KisSelectionToolFactoryBase() override; protected: QList createActionsImpl() override; }; -class KRITAUI_EXPORT KisToolPolyLineFactoryBase : public KisSelectionToolFactoryBase +class KRITAUI_EXPORT KisToolPolyLineFactoryBase : public KisToolPaintFactoryBase { public: explicit KisToolPolyLineFactoryBase(const QString &id); ~KisToolPolyLineFactoryBase() override; protected: QList createActionsImpl() override; }; #endif diff --git a/libs/widgets/KoTagChooserWidget.cpp b/libs/widgets/KoTagChooserWidget.cpp index 93487dbb30..d5c56bce81 100644 --- a/libs/widgets/KoTagChooserWidget.cpp +++ b/libs/widgets/KoTagChooserWidget.cpp @@ -1,199 +1,199 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTagChooserWidget.h" #include #include #include #include #include #include #include "KoResourceItemChooserContextMenu.h" #include "KoTagToolButton.h" class Q_DECL_HIDDEN KoTagChooserWidget::Private { public: SqueezedComboBox* comboBox; KoTagToolButton* tagToolButton; QStringList readOnlyTags; QStringList tags; }; KoTagChooserWidget::KoTagChooserWidget(QWidget* parent): QWidget(parent) , d(new Private()) { d->comboBox = new SqueezedComboBox(this); d->comboBox->setToolTip(i18n("Tag")); d->comboBox->setSizePolicy(QSizePolicy::MinimumExpanding , QSizePolicy::Fixed ); QGridLayout* comboLayout = new QGridLayout(this); comboLayout->addWidget(d->comboBox, 0, 0); d->tagToolButton = new KoTagToolButton(this); comboLayout->addWidget(d->tagToolButton, 0, 1); comboLayout->setSpacing(0); comboLayout->setMargin(0); comboLayout->setColumnStretch(0, 3); this->setEnabled(true); clear(); - connect(d->comboBox, SIGNAL(currentIndexChanged(QString)), + connect(d->comboBox, SIGNAL(currentTextChanged(QString)), this, SIGNAL(tagChosen(QString))); connect(d->tagToolButton, SIGNAL(popupMenuAboutToShow()), this, SLOT (tagOptionsContextMenuAboutToShow())); connect(d->tagToolButton, SIGNAL(newTagRequested(QString)), this, SIGNAL(newTagRequested(QString))); connect(d->tagToolButton, SIGNAL(deletionOfCurrentTagRequested()), this, SLOT(contextDeleteCurrentTag())); connect(d->tagToolButton, SIGNAL(renamingOfCurrentTagRequested(QString)), this, SLOT(tagRenamingRequested(QString))); connect(d->tagToolButton, SIGNAL(undeletionOfTagRequested(QString)), this, SIGNAL(tagUndeletionRequested(QString))); connect(d->tagToolButton, SIGNAL(purgingOfTagUndeleteListRequested()), this, SIGNAL(tagUndeletionListPurgeRequested())); } KoTagChooserWidget::~KoTagChooserWidget() { delete d; } void KoTagChooserWidget::contextDeleteCurrentTag() { if (selectedTagIsReadOnly()) { return; } emit tagDeletionRequested(currentlySelectedTag()); } void KoTagChooserWidget::tagRenamingRequested(const QString& newName) { if (newName.isEmpty() || selectedTagIsReadOnly()) { return; } emit tagRenamingRequested(currentlySelectedTag(), newName); } void KoTagChooserWidget::setUndeletionCandidate(const QString& tag) { d->tagToolButton->setUndeletionCandidate(tag); } void KoTagChooserWidget::setCurrentIndex(int index) { d->comboBox->setCurrentIndex(index); } int KoTagChooserWidget::findIndexOf(QString tagName) { return d->comboBox->findOriginalText(tagName); } void KoTagChooserWidget::addReadOnlyItem(QString tagName) { d->readOnlyTags.append(tagName); } void KoTagChooserWidget::insertItem(QString tagName) { QStringList tags = allTags(); tags.append(tagName); tags.sort(); foreach (QString readOnlyTag, d->readOnlyTags) { tags.prepend(readOnlyTag); } int index = tags.indexOf(tagName); if (d->comboBox->findOriginalText(tagName) == -1) { d->comboBox->insertSqueezedItem(tagName, index); d->tags.append(tagName); } } QString KoTagChooserWidget::currentlySelectedTag() { return d->comboBox->itemHighlighted(); } QStringList KoTagChooserWidget::allTags() { return d->tags; } bool KoTagChooserWidget::selectedTagIsReadOnly() { return d->readOnlyTags.contains(d->comboBox->itemHighlighted()) ; } void KoTagChooserWidget::addItems(QStringList tagNames) { d->tags.append(tagNames); d->tags.removeDuplicates(); d->tags.sort(); d->readOnlyTags.sort(); QStringList items = d->readOnlyTags + d->tags; items.removeDuplicates(); d->comboBox->resetOriginalTexts(items); } void KoTagChooserWidget::clear() { d->comboBox->resetOriginalTexts(QStringList()); } void KoTagChooserWidget::removeItem(QString item) { int pos = findIndexOf(item); if (pos >= 0) { d->comboBox->removeSqueezedItem(pos); d->tags.removeOne(item); } } void KoTagChooserWidget::tagOptionsContextMenuAboutToShow() { /* only enable the save button if the selected tag set is editable */ d->tagToolButton->readOnlyMode(selectedTagIsReadOnly()); emit popupMenuAboutToShow(); } void KoTagChooserWidget::showTagToolButton(bool show) { d->tagToolButton->setVisible(show); } diff --git a/plugins/extensions/pykrita/sip/CMakeLists.txt b/plugins/extensions/pykrita/sip/CMakeLists.txt index 754df9ba41..49ff5e1717 100644 --- a/plugins/extensions/pykrita/sip/CMakeLists.txt +++ b/plugins/extensions/pykrita/sip/CMakeLists.txt @@ -1,30 +1,30 @@ include(SIPMacros) message( ${SIP_VERSION} " - The version of SIP found expressed as a 6 digit hex number suitable for comparison as a string.") message( ${SIP_VERSION_STR} " - The version of SIP found as a human readable string.") message( ${SIP_EXECUTABLE} " - Path and filename of the SIP command line executable.") message( ${SIP_INCLUDE_DIR} " - Directory holding the SIP C++ header file.") message( ${SIP_DEFAULT_SIP_DIR} " - default SIP dir" ) set(SIP_INCLUDES ${SIP_DEFAULT_SIP_DIR} ${PYQT5_SIP_DIR} ${PYQT_SIP_DIR_OVERRIDE} ./krita) set(SIP_CONCAT_PARTS 1) set(SIP_TAGS ALL WS_X11 ${PYQT5_VERSION_TAG}) -set(SIP_EXTRA_OPTIONS -g -x PyKDE_QVector ${PYQT5_SIP_NAME}) +set(SIP_EXTRA_OPTIONS -g -o -x PyKDE_QVector ${PYQT5_SIP_NAME}) set(PYTHON_SITE_PACKAGES_INSTALL_DIR ${LIB_INSTALL_DIR}/krita-python-libs) file(GLOB PYKRITA_KRITA_sip_files ./krita/*.sip) set(SIP_EXTRA_FILES_DEPEND ${PYKRITA_KRITA_sip_files}) add_sip_python_module(PyKrita.krita ./krita/kritamod.sip kritalibkis kritaui kritaimage kritalibbrush) if (ENABLE_PYTHON_2) # Add an init file to turn it into a valid py2 module. # Otherwise PyKrita cannot be loaded. install(FILES ./__init__.py DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR}/PyKrita) endif (ENABLE_PYTHON_2) diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index 90af54d710..1e3e8494d1 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,753 +1,753 @@ /* * 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 // 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" 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_name.startsWith("/")) { - m_name.remove(0, 1); - } + if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (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); - dbgFile << "Visit: " << layer->name() << " colorSpace: " << layer->colorSpace()->id(); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); // HACK ALERT: we set the same filter again to ensure the layer // is correctly updated KisFilterConfigurationSP filter = layer->filter(); result = loadFilterConfiguration(filter.data(), getLocation(layer, DOT_FILTERCONFIG)); layer->setFilter(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); layer->setCopyFrom(srcLayer); // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(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->filter().data(), getLocation(mask, DOT_FILTERCONFIG)); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector 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(); // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); if (device->setProfile(profile)) { return true; } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location) { if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } 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/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index afa41865f0..4914a5f8b0 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1245 +1,1245 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_loader.h" #include #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 "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_keyframe_channel.h" #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QVector paletteFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; - QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } + if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisProofingConfigurationSP proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { - image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); + image = new KisImage(m_d->document->createUndoStore(), width, height, cs, m_d->imageName); } else { - image = new KisImage(0, width, height, cs, name); + image = new KisImage(0, width, height, cs, m_d->imageName); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName() == GLOBALASSISTANTSCOLOR) { if (e.hasAttribute(SIMPLECOLORDATA)) { QString colorData = e.attribute(SIMPLECOLORDATA); m_d->document->setAssistantsGlobalColor(KisDomUtils::qStringToQColor(colorData)); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } // reading palettes from XML for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { QDomElement e = child.toElement(); if (e.tagName() == PALETTES) { for (QDomElement paletteElement = e.lastChildElement(); !paletteElement.isNull(); paletteElement = paletteElement.previousSiblingElement()) { QString paletteName = paletteElement.attribute("filename"); m_d->paletteFilenames.append(paletteName); } break; } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); if (!proofingConfig) { proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); if (proofingProfile->valid()){ KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->document->shapeController(), m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResourceSP collection(new KisPSDLayerStyleCollectionResource("Embedded Styles.asl")); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } void KisKraLoader::loadPalettes(KoStore *store, KisDocument *doc) { QList list; Q_FOREACH (const QString &filename, m_d->paletteFilenames) { KoColorSetSP newPalette(new KoColorSet(filename)); store->open(m_d->imageName + PALETTE_PATH + filename); QByteArray data = store->read(store->size()); newPalette->fromByteArray(data); newPalette->setIsGlobal(false); newPalette->setIsEditable(true); store->close(); list.append(newPalette); } doc->setPaletteList(list); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; const QColor globalColor = m_d->document->assistantsGlobalColor(); QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); assistant->setAssistantGlobalColorCache(globalColor); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, colorSpace); else if (nodeType == FILE_LAYER) node = loadFileLayer(element, image, name, opacity); else if (nodeType == REFERENCE_IMAGES_LAYER) node = loadReferenceImagesLayer(element, image); else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); if (node->inherits("KisPaintLayer")) { - KisPaintLayer* layer = qobject_cast(node.data()); - QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); + KisPaintLayer* layer = qobject_cast(node.data()); + QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename)); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; QString legacy = filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } //get deprecated filters. if (filtername=="brightnesscontrast") { legacy = filtername; filtername = "perchannel"; } if (filtername=="left edge detections" || filtername=="right edge detections" || filtername=="top edge detections" || filtername=="bottom edge detections") { legacy = filtername; filtername = "edge detection"; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); kfc->setProperty("legacy", legacy); if (legacy=="brightnesscontrast") { kfc->setProperty("colorModel", cs->colorModelId().id()); } // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeControllerBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisNodeUuidInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisNodeUuidInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisNodeUuidInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element) { QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element) { KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, const KoColorSpace *colorSpace) { KisColorizeMaskSP mask = new KisColorizeMask(); const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; const bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0")); const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4")); const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0")); const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0")); const bool limitToDevice = KisDomUtils::toInt(element.attribute(COLORIZE_LIMIT_TO_DEVICE, "0")); mask->setUseEdgeDetection(useEdgeDetection); mask->setEdgeDetectionSize(edgeDetectionSize); mask->setFuzzyRadius(radius); mask->setCleanUpAmount(qreal(cleanUp) / 100.0); mask->setLimitToDeviceBounds(limitToDevice); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } KisNodeSP KisKraLoader::loadReferenceImagesLayer(const KoXmlElement &elem, KisImageSP image) { KisSharedPtr layer = new KisReferenceImagesLayer(m_d->document->shapeController(), image); m_d->document->setReferenceImagesLayer(layer, false); for (QDomElement child = elem.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toLower() == "referenceimage") { auto* reference = KisReferenceImage::fromXml(child); layer->addShape(reference); } } return layer; } diff --git a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop index 7daf0dfc3d..ef90200850 100644 --- a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop +++ b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop @@ -1,24 +1,28 @@ [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 del Python Name[ca@valencia]=Importador de connectors del Python +Name[gl]=Importador de complementos de Python Name[nl]=Importeur van Plugin voor Python Name[pl]=Import wtyczek Pythona Name[pt]=Importador de 'Plugins' do Python Name[sv]=Python-insticksimport Name[uk]=Засіб імпортування додатків Python Name[x-test]=xxPython Plugin Importerxx +Name[zh_CN]=Python 插件导入器 Comment=Imports Python plugins from zip files. Comment[ca]=Importa connectors del Python a partir de fitxers «zip». Comment[ca@valencia]=Importa connectors del Python a partir de fitxers «zip». +Comment[gl]=Importa complementos de Python de ficheiros zip. Comment[nl]=Importeert Python-plug-ins uit zip-bestanden. 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[uk]=Імпортує додатки Python з файлів zip. Comment[x-test]=xxImports Python plugins from zip files.xx +Comment[zh_CN]=从 zip 文件导入 Python 插件。 diff --git a/plugins/python/plugin_importer/plugin_importer.py b/plugins/python/plugin_importer/plugin_importer.py index 4e62d0b68b..2e7c460c1c 100644 --- a/plugins/python/plugin_importer/plugin_importer.py +++ b/plugins/python/plugin_importer/plugin_importer.py @@ -1,247 +1,248 @@ # Copyright (c) 2019 Rebecca Breu # This file is part of Krita. # Krita is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # Krita is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with Krita. If not, see . """This module provides the actual importing logic. See `:class:PluginImporter` for more info. For easy command line testing, call this module like this: > python plugin_importer.py foo.zip /output/path """ from configparser import ConfigParser, Error as ConfigParserError import os import shutil import sys from tempfile import TemporaryDirectory import zipfile from xml.etree import ElementTree class PluginImportError(Exception): """Base class for all exceptions of this module.""" pass class NoPluginsFoundException(PluginImportError): """No valid plugins can be found in the zip file.""" pass class PluginReadError(PluginImportError): """Zip file can't be read or its content can't be parsed.""" pass class PluginImporter: """Import a Krita Python Plugin from a zip file into the given directory. The Importer makes barely any assumptions about the file structure in the zip file. It will find one or more plugins with the following strategy: 1. Find files with the ending `.desktop` and read the Python module name from them 2. Find directories that correspond to the Python module names and that contain an `__init__.py` file 3. Find files with ending `.action` that have matching `` tags (these files are optional) 4. Extract the desktop- and action-files and the Python module directories into the corresponding pykrita and actions folders Usage: >>> importer = PluginImporter( '/path/to/plugin.zip', '/path/to/krita/resources/', confirm_overwrite_callback) >>> imported = importer.import_all() """ def __init__(self, zip_filename, resources_dir, confirm_overwrite_callback): """Initialise the importer. :param zip_filename: Filename of the zip archive containing the plugin(s) :param resources_dir: The Krita resources directory into which to extract the plugin(s) :param confirm_overwrite_callback: A function that gets called if a plugin already exists in the resources directory. It gets called with a dictionary of information about the plugin and should return whether the user wants to overwrite the plugin (True) or not (False). """ self.resources_dir = resources_dir self.confirm_overwrite_callback = confirm_overwrite_callback try: self.archive = zipfile.ZipFile(zip_filename) except(zipfile.BadZipFile, zipfile.LargeZipFile, OSError) as e: raise PluginReadError(str(e)) self.desktop_filenames = [] self.action_filenames = [] for filename in self.archive.namelist(): if filename.endswith('.desktop'): self.desktop_filenames.append(filename) if filename.endswith('.action'): self.action_filenames.append(filename) @property def destination_pykrita(self): dest = os.path.join(self.resources_dir, 'pykrita') if not os.path.exists(dest): os.mkdir(dest) return dest @property def destination_actions(self): dest = os.path.join(self.resources_dir, 'actions') if not os.path.exists(dest): os.mkdir(dest) return dest def get_destination_module(self, plugin): return os.path.join(self.destination_pykrita, plugin['name']) def get_destination_desktop(self, plugin): return os.path.join( self.destination_pykrita, '%s.desktop' % plugin['name']) def get_destination_actionfile(self, plugin): return os.path.join( self.destination_actions, '%s.action' % plugin['name']) def get_source_module(self, name): namelist = self.archive.namelist() for filename in namelist: - if filename.endswith('/%s/' % name): + if (filename.endswith('/%s/' % name) + or filename == '%s/' % name): # Sanity check: There should be an __init__.py inside if ('%s__init__.py' % filename) in namelist: return filename def get_source_actionfile(self, name): for filename in self.action_filenames: try: root = ElementTree.fromstring( self.archive.read(filename).decode('utf-8')) except ElementTree.ParseError as e: raise PluginReadError( '%s: %s' % (i18n('Action file'), str(e))) for action in root.findall('./Actions/Action'): if action.get('name') == name: return filename def read_desktop_config(self, desktop_filename): config = ConfigParser() try: config.read_string( self.archive.read(desktop_filename).decode('utf-8')) except ConfigParserError as e: raise PluginReadError( '%s: %s' % (i18n('Desktop file'), str(e))) return config def get_plugin_info(self): names = [] for filename in self.desktop_filenames: config = self.read_desktop_config(filename) try: name = config['Desktop Entry']['X-KDE-Library'] ui_name = config['Desktop Entry']['Name'] except KeyError as e: raise PluginReadError( 'Desktop file: Key %s not found' % str(e)) module = self.get_source_module(name) if module: names.append({ 'name': name, 'ui_name': ui_name, 'desktop': filename, 'module': module, 'action': self.get_source_actionfile(name) }) return names def extract_desktop(self, plugin): with open(self.get_destination_desktop(plugin), 'wb') as f: f.write(self.archive.read(plugin['desktop'])) def extract_module(self, plugin): with TemporaryDirectory() as tmp_dir: for name in self.archive.namelist(): if name.startswith(plugin['module']): self.archive.extract(name, tmp_dir) module_dirname = os.path.join( tmp_dir, *plugin['module'].split('/')) try: shutil.rmtree(self.get_destination_module(plugin)) except FileNotFoundError: pass shutil.copytree(module_dirname, self.get_destination_module(plugin)) def extract_actionfile(self, plugin): with open(self.get_destination_actionfile(plugin), 'wb') as f: f.write(self.archive.read(plugin['action'])) def extract_plugin(self, plugin): # Check if the plugin already exists in the source directory: if (os.path.exists(self.get_destination_desktop(plugin)) or os.path.exists(self.get_destination_module(plugin))): confirmed = self.confirm_overwrite_callback(plugin) if not confirmed: return False self.extract_desktop(plugin) self.extract_module(plugin) if plugin['action']: self.extract_actionfile(plugin) return True def import_all(self): """Imports all plugins from the zip archive. Returns a list of imported plugins. """ plugins = self.get_plugin_info() if not plugins: raise NoPluginsFoundException(i18n('No plugins found in archive')) imported = [] for plugin in plugins: success = self.extract_plugin(plugin) if success: imported.append(plugin) return imported if __name__ == '__main__': def callback(plugin): print('Overwriting plugin:', plugin['ui_name']) return True imported = PluginImporter( sys.argv[1], sys.argv[2], callback).import_all() for plugin in imported: print('Imported plugin:', plugin['ui_name']) diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo.desktop new file mode 100644 index 0000000000..30c4cdf613 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_missing_keys_desktop_file/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_desktop_file/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo.desktop new file mode 100644 index 0000000000..622eb6fda1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=foo +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo/foo.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_init_file/plugin/foo/foo.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo.desktop new file mode 100644 index 0000000000..c7bb8d0d48 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=wrong_name +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_no_matching_plugindir/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo.action new file mode 100644 index 0000000000..95352ffd7b --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo.action @@ -0,0 +1 @@ +this is wrong diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo.desktop new file mode 100644 index 0000000000..622eb6fda1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=foo +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_action_file/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo.desktop new file mode 100644 index 0000000000..04552f5f74 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo.desktop @@ -0,0 +1 @@ +this is wrong \ No newline at end of file diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/fail_unparsable_desktop_file/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/foo.desktop new file mode 100644 index 0000000000..622eb6fda1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=foo +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/somedir/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/somedir/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/somedir/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/somedir/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/somedir/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_nested/plugin/somedir/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo.desktop new file mode 100644 index 0000000000..622eb6fda1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=foo +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_no_action/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo.action b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo.desktop new file mode 100644 index 0000000000..622eb6fda1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=foo +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_simple/plugin/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo.action b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo.action new file mode 100644 index 0000000000..517920fef1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo.action @@ -0,0 +1,19 @@ + + + + Foo + + + + Foo... + + + + 0 + 0 + + false + + + + diff --git a/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo.desktop b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo.desktop new file mode 100644 index 0000000000..622eb6fda1 --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=foo +X-Python-2-Compatible=false +Name=Foo diff --git a/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo/__init__.py b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo/__init__.py new file mode 100644 index 0000000000..b376c9941f --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo/__init__.py @@ -0,0 +1 @@ +print('hello') diff --git a/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo/foo.py b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo/foo.py new file mode 100644 index 0000000000..b98f02ce6e --- /dev/null +++ b/plugins/python/plugin_importer/tests/fixtures/success_toplevel/foo/foo.py @@ -0,0 +1 @@ +print('foo') diff --git a/plugins/python/plugin_importer/tests/test_plugin_importer.py b/plugins/python/plugin_importer/tests/test_plugin_importer.py index 4fcf2d4264..f402cba1e3 100644 --- a/plugins/python/plugin_importer/tests/test_plugin_importer.py +++ b/plugins/python/plugin_importer/tests/test_plugin_importer.py @@ -1,37 +1,300 @@ +"""Unit tests for the plugin_importer module. + +Unit tests can either be toplevel functions whose names start with +`test_`, or they can be class methods on classes derived from +`unittest.TestCase`; again the method names need to start with `test_`. +""" + + import os import pytest from tempfile import TemporaryDirectory -from unittest import TestCase - +from unittest import mock, TestCase +from zipfile import ZipFile -from .. plugin_importer import PluginImporter, PluginReadError +from .. plugin_importer import ( + NoPluginsFoundException, + PluginImporter, + PluginReadError, +) class PluginImporterTestCase(TestCase): + """Collection of unit tests for the plugin_importer module. + + Since the tests need a bit of setup for file system operations, we gather + them together in a TestCase class. + """ def setUp(self): + """This method will be run before each test in this class. + + We create temporary directories for creating and extracting + zip files: `resources_dir` will be the destination (a stand-in + for Krita's resources dir), `plugin_dir` the source where our + plugin zip is located. + """ + self.resources_dir = TemporaryDirectory() self.plugin_dir = TemporaryDirectory() def tearDown(self): + """This method will be run after each test in this class. + + We remove the temporary directories created in `setUp`. + """ + self.resources_dir.cleanup() self.plugin_dir.cleanup() @property def zip_filename(self): - return os.path.join( - self.resources_dir.name, 'plugin.zip') + """Helper method for easy access to the full path of the zipped + plugin. + """ + return os.path.join(self.plugin_dir.name, 'plugin.zip') + + def zip_plugin(self, dirname): + """Helper method to zip a plugin from the subdirectory `dirname` in + the `fixtures` folder and store it in the temporary directory + `self.plugin_dir`. + + The `fixtures` directory contains the non-zipped plugins for easier + maintenance. + """ + + # Get the full name of the folder this file resides in: + testroot = os.path.dirname(os.path.realpath(__file__)) + + # From that, we can get the full name of the plugin fixture folder: + src = os.path.join(testroot, 'fixtures', dirname) + + # Zip it: + with ZipFile(self.zip_filename, 'w') as plugin_zip: + for root, dirs, files in os.walk(src): + dirname = root.replace(src, '') + for filename in files + dirs: + plugin_zip.write( + filename=os.path.join(root, filename), + arcname=os.path.join(dirname, filename)) + + def assert_in_resources_dir(self, *path): + """Helper method to check whether a directory or file exists inside + `self.resources_dir`. + """ + + assert os.path.exists(os.path.join(self.resources_dir.name, *path)) + + def assert_not_in_resources_dir(self, *path): + """Helper method to check whether a directory or file doesn't exist + inside `self.resources_dir`. + """ + + assert not os.path.exists(os.path.join(self.resources_dir.name, *path)) + + ############################################################ + # The actual tests start below def test_zipfile_doesnt_exist(self): + """Test: Import a filename that doesn't exist.""" + + # Create nothing here... + with pytest.raises(PluginReadError): + # We expect an exception PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) def test_zipfile_not_a_zip(self): + """Test: Import a file that isn't a zip file.""" + + # Create a text file: with open(self.zip_filename, 'w') as f: f.write('foo') + with pytest.raises(PluginReadError): + # We expect an exception PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) + + def test_simple_plugin_success(self): + """Test: Import a basic plugin.""" + + self.zip_plugin('success_simple') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + imported = importer.import_all() + assert len(imported) == 1 + self.assert_in_resources_dir('pykrita', 'foo') + self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') + self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') + self.assert_in_resources_dir('pykrita', 'foo.desktop') + self.assert_in_resources_dir('actions', 'foo.action') + + def test_toplevel_plugin_success(self): + """Test: Import a plugin with everything at toplevel.""" + + self.zip_plugin('success_toplevel') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + imported = importer.import_all() + assert len(imported) == 1 + self.assert_in_resources_dir('pykrita', 'foo') + self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') + self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') + self.assert_in_resources_dir('pykrita', 'foo.desktop') + self.assert_in_resources_dir('actions', 'foo.action') + + def test_nested_plugin_success(self): + """Test: Import a plugin with nested directories.""" + + self.zip_plugin('success_nested') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + imported = importer.import_all() + assert len(imported) == 1 + self.assert_in_resources_dir('pykrita', 'foo') + self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') + self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') + self.assert_in_resources_dir('pykrita', 'foo.desktop') + self.assert_in_resources_dir('actions', 'foo.action') + + def test_no_action_success(self): + """Test: Import a plugin without action file. + + Should import fine without creating an action file.""" + + self.zip_plugin('success_no_action') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + imported = importer.import_all() + assert len(imported) == 1 + self.assert_in_resources_dir('pykrita', 'foo') + self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') + self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') + self.assert_in_resources_dir('pykrita', 'foo.desktop') + self.assert_not_in_resources_dir('actions', 'foo.action') + + def test_overwrite_existing(self): + """Test: Overwrite an existing plugin when overwrite confirmed.""" + + self.zip_plugin('success_simple') + + # Create an existing python module in the the resources directory: + plugin_dir = os.path.join(self.resources_dir.name, 'pykrita', 'foo') + os.makedirs(plugin_dir) + init_file = os.path.join(plugin_dir, '__init__.py') + with open(init_file, 'w') as f: + f.write('# existing module') + + # Create a mock callback on which we can test that it has been called: + confirm_callback = mock.MagicMock(return_value=True) + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + confirm_callback) + imported = importer.import_all() + assert len(imported) == 1 + assert confirm_callback.called + + # Existing plugin should be overwritten: + with open(init_file, 'r') as f: + assert f.read().strip() == "print('hello')" + + def test_dont_overwrite_existing(self): + """Test: Don't overwrite an existing plugin when overwrite not + confirmed.""" + + self.zip_plugin('success_simple') + + # Create an existing python module in the the resources directory: + plugin_dir = os.path.join(self.resources_dir.name, 'pykrita', 'foo') + os.makedirs(plugin_dir) + init_file = os.path.join(plugin_dir, '__init__.py') + with open(init_file, 'w') as f: + f.write('# existing module') + + # Create a mock callback on which we can test that it has been called: + confirm_callback = mock.MagicMock(return_value=False) + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + confirm_callback) + imported = importer.import_all() + assert len(imported) == 0 + assert confirm_callback.called + + # Existing plugin should not be overwritten: + with open(init_file, 'r') as f: + assert f.read().strip() == '# existing module' + + def test_missing_desktop_file(self): + """Test: Import plugin without a desktop file.""" + + self.zip_plugin('fail_no_desktop_file') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + with pytest.raises(NoPluginsFoundException): + # We expect an exception + importer.import_all() + + def test_unparsable_desktop_file(self): + """Test: Import plugin whose desktop file is not parsable.""" + + self.zip_plugin('fail_unparsable_desktop_file') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + with pytest.raises(PluginReadError): + # We expect an exception + importer.import_all() + + def test_missing_keys_in_desktop_file(self): + """Test: Import plugin whose destkop file is missing needed keys.""" + + self.zip_plugin('fail_missing_keys_desktop_file') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + with pytest.raises(PluginReadError): + # We expect an exception + importer.import_all() + + def test_no_matching_plugindir(self): + """Test: Import plugin whose destkop file is missing needed keys.""" + + self.zip_plugin('fail_no_matching_plugindir') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + with pytest.raises(NoPluginsFoundException): + # We expect an exception + importer.import_all() + + def test_no_init_file(self): + """Test: Import plugin whose python module is missing the __init__.py + file.""" + + self.zip_plugin('fail_no_init_file') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + with pytest.raises(NoPluginsFoundException): + # We expect an exception + importer.import_all() + + def test_unparsable_action_file(self): + """Test: Import plugin whose action file isn't parsable.""" + + self.zip_plugin('fail_unparsable_action_file') + importer = PluginImporter(self.zip_filename, + self.resources_dir.name, + lambda x: True) + with pytest.raises(PluginReadError): + # We expect an exception + importer.import_all() diff --git a/plugins/tools/basictools/kis_tool_ellipse.h b/plugins/tools/basictools/kis_tool_ellipse.h index 79fd0c9460..f45110c28a 100644 --- a/plugins/tools/basictools/kis_tool_ellipse.h +++ b/plugins/tools/basictools/kis_tool_ellipse.h @@ -1,74 +1,74 @@ /* * kis_tool_ellipse.h - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Clarence Dang * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_ELLIPSE_H__ #define __KIS_TOOL_ELLIPSE_H__ #include "kis_tool_shape.h" #include "kis_types.h" -#include "KisSelectionToolFactoryBase.h" +#include "KisToolPaintFactoryBase.h" #include "flake/kis_node_shape.h" #include #include class KoCanvasBase; class KisToolEllipse : public KisToolEllipseBase { Q_OBJECT public: KisToolEllipse(KoCanvasBase * canvas); ~KisToolEllipse() override; protected Q_SLOTS: void resetCursorStyle() override; protected: void finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) override; }; -class KisToolEllipseFactory : public KisSelectionToolFactoryBase +class KisToolEllipseFactory : public KisToolPaintFactoryBase { public: KisToolEllipseFactory() - : KisSelectionToolFactoryBase("KritaShape/KisToolEllipse") { + : KisToolPaintFactoryBase("KritaShape/KisToolEllipse") { setToolTip(i18n("Ellipse Tool")); setSection(TOOL_TYPE_SHAPE); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("krita_tool_ellipse")); setPriority(3); } ~KisToolEllipseFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolEllipse(canvas); } }; #endif //__KIS_TOOL_ELLIPSE_H__ diff --git a/plugins/tools/basictools/kis_tool_rectangle.h b/plugins/tools/basictools/kis_tool_rectangle.h index c12392b584..5c01d93f9b 100644 --- a/plugins/tools/basictools/kis_tool_rectangle.h +++ b/plugins/tools/basictools/kis_tool_rectangle.h @@ -1,79 +1,79 @@ /* * kis_tool_rectangle.h - part of KImageShop^WKrayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_RECTANGLE_H__ #define __KIS_TOOL_RECTANGLE_H__ #include "kis_tool_shape.h" #include "kis_types.h" -#include "KisSelectionToolFactoryBase.h" +#include "KisToolPaintFactoryBase.h" #include "flake/kis_node_shape.h" #include #include class QRect; class KoCanvasBase; class KisToolRectangle : public KisToolRectangleBase { Q_OBJECT public: KisToolRectangle(KoCanvasBase * canvas); ~KisToolRectangle() override; protected: void finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) override; protected Q_SLOTS: void resetCursorStyle() override; }; -class KisToolRectangleFactory : public KisSelectionToolFactoryBase +class KisToolRectangleFactory : public KisToolPaintFactoryBase { public: KisToolRectangleFactory() - : KisSelectionToolFactoryBase("KritaShape/KisToolRectangle") { + : KisToolPaintFactoryBase("KritaShape/KisToolRectangle") { setToolTip(i18n("Rectangle Tool")); setSection(TOOL_TYPE_SHAPE); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("krita_tool_rectangle")); //setShortcut( Qt::Key_F6 ); setPriority(2); } ~KisToolRectangleFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolRectangle(canvas); } }; #endif // __KIS_TOOL_RECTANGLE_H__ diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.h b/plugins/tools/selectiontools/kis_tool_select_contiguous.h index c3f5b875a3..b4f1cb8730 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.h +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.h @@ -1,95 +1,95 @@ /* * kis_tool_select_contiguous.h - part of KImageShop^WKrayon^Krita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_SELECT_CONTIGUOUS_H__ #define __KIS_TOOL_SELECT_CONTIGUOUS_H__ -#include "KoToolFactoryBase.h" +#include "KisSelectionToolFactoryBase.h" #include "kis_tool_select_base.h" #include #include #include /** * The 'magic wand' selection tool -- in fact just * a floodfill that only creates a selection. */ class KisToolSelectContiguous : public KisToolSelect { Q_OBJECT public: KisToolSelectContiguous(KoCanvasBase *canvas); ~KisToolSelectContiguous() override; QWidget* createOptionWidget() override; void paint(QPainter &painter, const KoViewConverter &converter) override; void beginPrimaryAction(KoPointerEvent *event) override; void resetCursorStyle(); protected: bool wantsAutoScroll() const override { return false; } public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; virtual void slotSetFuzziness(int); virtual void slotSetSizemod(int); virtual void slotSetFeather(int); virtual void slotLimitToCurrentLayer(int); //virtual bool antiAliasSelection(); protected: using KisToolSelectBase::m_widgetHelper; private: int m_fuzziness; int m_sizemod; int m_feather; bool m_limitToCurrentLayer; KConfigGroup m_configGroup; }; -class KisToolSelectContiguousFactory : public KoToolFactoryBase +class KisToolSelectContiguousFactory : public KisSelectionToolFactoryBase { public: KisToolSelectContiguousFactory() - : KoToolFactoryBase("KisToolSelectContiguous") + : KisSelectionToolFactoryBase("KisToolSelectContiguous") { setToolTip(i18n("Contiguous Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_contiguous_selection")); setPriority(4); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectContiguousFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectContiguous(canvas); } }; #endif //__KIS_TOOL_SELECT_CONTIGUOUS_H__ diff --git a/plugins/tools/selectiontools/kis_tool_select_elliptical.h b/plugins/tools/selectiontools/kis_tool_select_elliptical.h index 4b29742910..ef6aee434c 100644 --- a/plugins/tools/selectiontools/kis_tool_select_elliptical.h +++ b/plugins/tools/selectiontools/kis_tool_select_elliptical.h @@ -1,91 +1,91 @@ /* * kis_tool_select_elliptical.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_SELECT_ELLIPTICAL_H__ #define __KIS_TOOL_SELECT_ELLIPTICAL_H__ -#include "KoToolFactoryBase.h" +#include "KisSelectionToolFactoryBase.h" #include "kis_tool_ellipse_base.h" #include #include "kis_selection_tool_config_widget_helper.h" #include #include #include #include class __KisToolSelectEllipticalLocal : public KisToolEllipseBase { Q_OBJECT public: __KisToolSelectEllipticalLocal(KoCanvasBase *canvas); bool hasUserInteractionRunning() const; protected: virtual SelectionMode selectionMode() const = 0; virtual SelectionAction selectionAction() const = 0; virtual bool antiAliasSelection() const = 0; private: void finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY) override; }; typedef KisToolSelectBase<__KisToolSelectEllipticalLocal> KisToolSelectEllipticalTemplate; class KisToolSelectElliptical : public KisToolSelectEllipticalTemplate { Q_OBJECT public: KisToolSelectElliptical(KoCanvasBase* canvas); void resetCursorStyle(); }; -class KisToolSelectEllipticalFactory : public KoToolFactoryBase +class KisToolSelectEllipticalFactory : public KisSelectionToolFactoryBase { public: KisToolSelectEllipticalFactory() - : KoToolFactoryBase("KisToolSelectElliptical") + : KisSelectionToolFactoryBase("KisToolSelectElliptical") { setToolTip(i18n("Elliptical Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_elliptical_selection")); setShortcut(QKeySequence(Qt::Key_J)); setPriority(1); } ~KisToolSelectEllipticalFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectElliptical(canvas); } }; #endif //__KIS_TOOL_SELECT_ELLIPTICAL_H__ diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.h b/plugins/tools/selectiontools/kis_tool_select_outline.h index 27c894a0d5..35b9b65f5d 100644 --- a/plugins/tools/selectiontools/kis_tool_select_outline.h +++ b/plugins/tools/selectiontools/kis_tool_select_outline.h @@ -1,93 +1,93 @@ /* * kis_tool_select_freehand.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_SELECT_OUTLINE_H_ #define KIS_TOOL_SELECT_OUTLINE_H_ #include -#include +#include "KisSelectionToolFactoryBase.h" #include #include class QPainterPath; class KisToolSelectOutline : public KisToolSelect { Q_OBJECT public: KisToolSelectOutline(KoCanvasBase *canvas); ~KisToolSelectOutline() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter& gc, const KoViewConverter &converter) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; void resetCursorStyle(); public Q_SLOTS: void deactivate() override; protected: using KisToolSelectBase::m_widgetHelper; private: void finishSelectionAction(); void updateFeedback(); void updateContinuedMode(); void updateCanvas(); QPainterPath m_paintPath; vQPointF m_points; bool m_continuedMode; QPointF m_lastCursorPos; }; -class KisToolSelectOutlineFactory : public KoToolFactoryBase +class KisToolSelectOutlineFactory : public KisSelectionToolFactoryBase { public: KisToolSelectOutlineFactory() - : KoToolFactoryBase("KisToolSelectOutline") + : KisSelectionToolFactoryBase("KisToolSelectOutline") { setToolTip(i18n("Outline Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_outline_selection")); setPriority(3); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectOutlineFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectOutline(canvas); } }; #endif //__selecttoolfreehand_h__ diff --git a/plugins/tools/selectiontools/kis_tool_select_path.h b/plugins/tools/selectiontools/kis_tool_select_path.h index 4382299545..0c9a4424c7 100644 --- a/plugins/tools/selectiontools/kis_tool_select_path.h +++ b/plugins/tools/selectiontools/kis_tool_select_path.h @@ -1,110 +1,110 @@ /* * Copyright (c) 2007 Sven Langkamp * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_SELECT_PATH_H_ #define KIS_TOOL_SELECT_PATH_H_ #include -#include +#include #include "kis_tool_select_base.h" #include "kis_delegated_tool.h" #include class KoCanvasBase; class KisToolSelectPath; class __KisToolSelectPathLocalTool : public KoCreatePathTool { public: __KisToolSelectPathLocalTool(KoCanvasBase * canvas, KisToolSelectPath* parentTool); void paintPath(KoPathShape &path, QPainter &painter, const KoViewConverter &converter) override; void addPathShape(KoPathShape* pathShape) override; using KoCreatePathTool::createOptionWidgets; using KoCreatePathTool::endPathWithoutLastPoint; using KoCreatePathTool::endPath; using KoCreatePathTool::cancelPath; using KoCreatePathTool::removeLastPoint; private: KisToolSelectPath* const m_selectionTool; }; typedef KisDelegatedTool DelegatedSelectPathTool; struct KisDelegatedSelectPathWrapper : public DelegatedSelectPathTool { KisDelegatedSelectPathWrapper(KoCanvasBase *canvas, const QCursor &cursor, KisTool* delegateTool) : DelegatedSelectPathTool(canvas, cursor, (__KisToolSelectPathLocalTool*) delegateTool) { } // If an event is explicitly forwarded only as an action (e.g. shift-click is captured by "change size") // we will receive a primary action but no mousePressEvent. Thus these events must be explicitly forwarded. void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; bool hasUserInteractionRunning() const; }; class KisToolSelectPath : public KisToolSelectBase { Q_OBJECT public: KisToolSelectPath(KoCanvasBase * canvas); void mousePressEvent(KoPointerEvent* event) override; bool eventFilter(QObject *obj, QEvent *event) override; void resetCursorStyle(); protected: void requestStrokeCancellation() override; void requestStrokeEnd() override; friend class __KisToolSelectPathLocalTool; QList > createOptionWidgets() override; }; -class KisToolSelectPathFactory : public KoToolFactoryBase +class KisToolSelectPathFactory : public KisSelectionToolFactoryBase { public: KisToolSelectPathFactory() - : KoToolFactoryBase("KisToolSelectPath") { + : KisSelectionToolFactoryBase("KisToolSelectPath") { setToolTip(i18n("Bezier Curve Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_path_selection")); setPriority(6); } ~KisToolSelectPathFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectPath(canvas); } }; #endif // KIS_TOOL_SELECT_PATH_H_ diff --git a/plugins/tools/selectiontools/kis_tool_select_polygonal.h b/plugins/tools/selectiontools/kis_tool_select_polygonal.h index 1777407ac0..0a74f56ea7 100644 --- a/plugins/tools/selectiontools/kis_tool_select_polygonal.h +++ b/plugins/tools/selectiontools/kis_tool_select_polygonal.h @@ -1,78 +1,78 @@ /* * kis_tool_select_polygonal.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_SELECT_POLYGONAL_H_ #define KIS_TOOL_SELECT_POLYGONAL_H_ -#include "KoToolFactoryBase.h" +#include "KisSelectionToolFactoryBase.h" #include "kis_tool_polyline_base.h" #include #include "kis_selection_tool_config_widget_helper.h" #include class __KisToolSelectPolygonalLocal : public KisToolPolylineBase { Q_OBJECT public: __KisToolSelectPolygonalLocal(KoCanvasBase *canvas); protected: virtual SelectionMode selectionMode() const = 0; virtual SelectionAction selectionAction() const = 0; virtual bool antiAliasSelection() const = 0; private: void finishPolyline(const QVector &points) override; private: }; class KisToolSelectPolygonal : public KisToolSelectBase<__KisToolSelectPolygonalLocal> { Q_OBJECT public: KisToolSelectPolygonal(KoCanvasBase* canvas); void resetCursorStyle(); }; -class KisToolSelectPolygonalFactory : public KoToolFactoryBase +class KisToolSelectPolygonalFactory : public KisSelectionToolFactoryBase { public: KisToolSelectPolygonalFactory() - : KoToolFactoryBase("KisToolSelectPolygonal") + : KisSelectionToolFactoryBase("KisToolSelectPolygonal") { setToolTip(i18n("Polygonal Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_polygonal_selection")); setPriority(2); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectPolygonalFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectPolygonal(canvas); } }; #endif //__selecttoolpolygonal_h__ diff --git a/plugins/tools/selectiontools/kis_tool_select_rectangular.h b/plugins/tools/selectiontools/kis_tool_select_rectangular.h index 7effa6e42e..3ce49b29e8 100644 --- a/plugins/tools/selectiontools/kis_tool_select_rectangular.h +++ b/plugins/tools/selectiontools/kis_tool_select_rectangular.h @@ -1,86 +1,86 @@ /* * kis_tool_select_rectangular.h - part of Krita * * Copyright (c) 1999 Michael Koch * 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. */ #ifndef KIS_TOOL_SELECT_RECTANGULAR_H_ #define KIS_TOOL_SELECT_RECTANGULAR_H_ -#include "KoToolFactoryBase.h" +#include "KisSelectionToolFactoryBase.h" #include "kis_tool_rectangle_base.h" #include #include "kis_selection_tool_config_widget_helper.h" #include #include class __KisToolSelectRectangularLocal : public KisToolRectangleBase { Q_OBJECT public: __KisToolSelectRectangularLocal(KoCanvasBase * canvas); bool hasUserInteractionRunning() const; protected: virtual SelectionMode selectionMode() const = 0; virtual SelectionAction selectionAction() const = 0; private: void finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) override; }; class KisToolSelectRectangular : public KisToolSelectBase<__KisToolSelectRectangularLocal> { Q_OBJECT public: KisToolSelectRectangular(KoCanvasBase* canvas); void resetCursorStyle(); }; -class KisToolSelectRectangularFactory : public KoToolFactoryBase +class KisToolSelectRectangularFactory : public KisSelectionToolFactoryBase { public: KisToolSelectRectangularFactory() - : KoToolFactoryBase("KisToolSelectRectangular") + : KisSelectionToolFactoryBase("KisToolSelectRectangular") { setToolTip(i18n("Rectangular Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_rect_selection")); setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); setPriority(0); } ~KisToolSelectRectangularFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectRectangular(canvas); } }; #endif // KIS_TOOL_SELECT_RECTANGULAR_H_ diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.h b/plugins/tools/selectiontools/kis_tool_select_similar.h index bbe6a8d313..6ce41645fb 100644 --- a/plugins/tools/selectiontools/kis_tool_select_similar.h +++ b/plugins/tools/selectiontools/kis_tool_select_similar.h @@ -1,75 +1,75 @@ /* * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_SELECT_SIMILAR_H_ #define KIS_TOOL_SELECT_SIMILAR_H_ -#include +#include #include #include #include "kis_tool_select_base.h" #include /* * Tool to select colors by pointing at a color on the image. */ class KisToolSelectSimilar: public KisToolSelect { Q_OBJECT public: KisToolSelectSimilar(KoCanvasBase * canvas); void beginPrimaryAction(KoPointerEvent *event) override; void paint(QPainter&, const KoViewConverter &) override {} QWidget* createOptionWidget() override; void resetCursorStyle(); public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void slotSetFuzziness(int); protected: using KisToolSelectBase::m_widgetHelper; private: int m_fuzziness; KConfigGroup m_configGroup; }; -class KisToolSelectSimilarFactory : public KoToolFactoryBase +class KisToolSelectSimilarFactory : public KisSelectionToolFactoryBase { public: KisToolSelectSimilarFactory() - : KoToolFactoryBase("KisToolSelectSimilar") + : KisSelectionToolFactoryBase("KisToolSelectSimilar") { setToolTip(i18n("Similar Color Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_similar_selection")); setPriority(5); } ~KisToolSelectSimilarFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectSimilar(canvas); } }; #endif // KIS_TOOL_SELECT_SIMILAR_H_ diff --git a/plugins/tools/tool_transform2/wdg_tool_transform.ui b/plugins/tools/tool_transform2/wdg_tool_transform.ui index 1243705a83..c9c378769e 100644 --- a/plugins/tools/tool_transform2/wdg_tool_transform.ui +++ b/plugins/tools/tool_transform2/wdg_tool_transform.ui @@ -1,2265 +1,2265 @@ WdgToolTransform 0 0 499 - 709 + 751 0 0 0 0 16777215 16777215 0 0 Qt::LeftToRight 4 QFrame::NoFrame QFrame::Plain 0 1 1 1 1 0 0 0 Free Free Transform true true true true 0 0 Perspective Perspective true true true 0 0 Warp Warp true false true true 0 0 Cage Cage true true true 0 0 Liquify Liquify true true true 0 0 Free Transform Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::StyledPanel QFrame::Raised 2 0 0 0 0 0 0 12 QLayout::SetFixedSize 10 10 20 10 15 0 0 Qt::RightToLeft &Filter: Qt::AlignCenter cmbFilter QLayout::SetDefaultConstraint 0 true 0 0 true true 0 0 true true 0 0 true true 0 0 true true true 0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 0 0 0 Transform around pivot point (Alt) true true Posi&tion true freeTransformRadioGroup &Rotate freeTransformRadioGroup Scale freeTransformRadioGroup Shear freeTransformRadioGroup 0 0 0 0 75 true Qt::LeftToRight false off canvas Qt::RichText false true Qt::NoTextInteraction Qt::Horizontal 40 20 true 0 0 240 100 Rotation false false false 2 5 0 7 0 QFormLayout::AllNonFixedFieldsGrow 5 5 0 0 0 0 Rotate around X-Axis Qt::LeftToRight &x: aXBox Rotate around X-Axis 0 0 0 0 Rotate around Y-Axis Qt::LeftToRight &y: aYBox Rotate around Y-Axis 0 0 Rotate around Z-Axis 0 0 0 0 Rotate around Z-Axis Qt::LeftToRight &z: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter aZBox 0 0 240 70 Scale false false false 2 5 0 0 5 0 0 0 0 Horizontal Scale w&idth: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter scaleXBox 0 0 Horizontal Scale % 0 0 Vertical Scale % 0 0 0 0 Vertical Scale &height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter scaleYBox 0 0 20 25 0 0 240 70 Position false false false 2 5 0 0 QFormLayout::AllNonFixedFieldsGrow 0 0 Horizontal Translation Qt::LeftToRight x: translateXBox 0 0 Horizontal Translation Qt::LeftToRight 0 0 Vertical Translation 0 0 Vertical Translation y: translateYBox 0 0 240 80 false Shear Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false false false 2 5 0 0 QFormLayout::AllNonFixedFieldsGrow 8 20 0 0 Horizontal Shear x: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter shearXBox 0 0 Vertical Shear 0 0 0 0 Vertical Shear y: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter shearYBox 0 0 Horizontal Shear Qt::Vertical QSizePolicy::Preferred 20 10 6 0 0 32 16777215 Flip selection horizontally 0 0 32 16777215 Flip selection vertically Qt::Horizontal QSizePolicy::Preferred 20 20 0 0 32 16777215 Rotate selection counter-clockwise 90 degrees 0 0 32 16777215 Rotate the selection clockwise 90 degrees Qt::Horizontal QSizePolicy::Expanding 20 20 Qt::Vertical QSizePolicy::Preferred 20 10 10 0 0 0 0 0 2 0 Fle&xibility: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter alphaBox 0 0 0 0 Anc&hor Strength: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter cmbWarpType false Anchor Points false false 0 0 0 0 0 0 0 Subdi&vide true true buttonGroup Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 2 30 3 0 0 0 0 0 0 0 Draw true buttonGroup false 0 0 0 0 0 Clear Points Lock Points Qt::Vertical 20 10 Qt::LeftToRight false Create 3 points on the canvas to begin Add/Ed&it Anchor Points true cageTransformButtonGroup true Defor&m Layer cageTransformButtonGroup Qt::Vertical QSizePolicy::Minimum 17 17 75 true Adjust Granularity : Preview 0 0 Real 0 0 Qt::Vertical - 17 - 411 + 20 + 10 0 0 0 <html><head/><body><p><br/></p></body></html> QFrame::NoFrame QFrame::Plain 0 2 2 15 0 0 Move true true true true 0 0 Scale true true true 0 0 Rotate true true true 0 0 Offset true true true 0 0 Undo true true true 3 5 0 0 51 0 Reverse: 0 0 49 0 Spacing: 0 0 32 0 Flow: 0 0 27 0 Size: 0 0 51 0 Amount: 0 0 38 0 Mode: 0 0 0 Build Up Wash true 0 0 true Pressure true true true Pressure true true true Qt::Vertical 20 0 6 6 0 0 32 16777215 Show Decorations true false 0 0 32 16777215 Work Recursively true true Qt::Horizontal 13 13 Qt::LeftToRight Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Reset true KisCmbIDList
widgets/kis_cmb_idlist.h
KoAspectButton QWidget
KoAspectButton.h
1
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
+ -