diff --git a/3rdparty/ext_pyqt/CMakeLists.txt b/3rdparty/ext_pyqt/CMakeLists.txt index 471a5e912a..2fbdd6ba4c 100644 --- a/3rdparty/ext_pyqt/CMakeLists.txt +++ b/3rdparty/ext_pyqt/CMakeLists.txt @@ -1,53 +1,54 @@ SET(PREFIX_ext_pyqt "${EXTPREFIX}" ) if (UNIX) SET(PYTHON_EXECUTABLE_PATH ${PREFIX_ext_sip}/bin/python3) if(NOT EXISTS ${PYTHON_EXECUTABLE_PATH}) message("WARNING: using system python3!") SET(PYTHON_EXECUTABLE_PATH python3) endif() ExternalProject_Add( ext_pyqt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/PyQt5_gpl-5.6.tar.gz URL_MD5 dbfc885c0548e024ba5260c4f44e0481 CONFIGURE_COMMAND ${PYTHON_EXECUTABLE_PATH} /configure.py --confirm-license --qmake ${PREFIX_ext_pyqt}/bin/qmake --sip ${PREFIX_ext_pyqt}/bin/sip --sip-incdir ${PREFIX_ext_pyqt}/include --sipdir ${PREFIX_ext_pyqt}/share/sip BUILD_COMMAND make INSTALL_COMMAND make install BUILD_IN_SOURCE 1 UPDATE_COMMAND "" ) elseif(MINGW) list(APPEND _PYQT_conf --confirm-license --target-py-version 3.6 --bindir ${PREFIX_ext_pyqt}/bin --qt ${PREFIX_ext_pyqt} --sip ${PREFIX_ext_pyqt}/bin/sip.exe --sip-incdir ${PREFIX_ext_pyqt}/include --spec win32-g++ --verbose --sipdir ${PREFIX_ext_pyqt}/share/sip --destdir ${PREFIX_ext_pyqt}/share/krita/pykrita + --stubsdir ${PREFIX_ext_pyqt}/share/krita/pykrita/PyQt5 --no-qml-plugin --no-python-dbus --no-qsci-api --no-tools --disable QtSql --disable QtTest --disable QtWinExtras ) ExternalProject_Add( ext_pyqt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.9/PyQt5_gpl-5.9.zip URL_MD5 d978884753df265896eda436d8f4e07b PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/pyqt-configure-fix.patch CONFIGURE_COMMAND ${PYTHON_EXECUTABLE} /configure.py ${_PYQT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} CXXFLAGS=-D_hypot=hypot LDFLAGS=${SECURITY_SHARED_LINKER_FLAGS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install BUILD_IN_SOURCE 1 UPDATE_COMMAND "" ) endif() diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd new file mode 100644 index 0000000000..37920aaa46 --- /dev/null +++ b/build-tools/windows/build.cmd @@ -0,0 +1,768 @@ +@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 gettext qt zlib boost eigen3 exiv2 fftw3 ilmbase +set EXT_TARGETS=%EXT_TARGETS% jpeg lcms2 ocio openexr png tiff gsl vc libraw +set EXT_TARGETS=%EXT_TARGETS% freetype poppler kwindowsystem drmingw gmic +set EXT_TARGETS=%EXT_TARGETS% python sip pyqt + +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. + +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... +"%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 ^ + -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/FindPyQt5.cmake b/cmake/modules/FindPyQt5.cmake index f4b142cb4d..6f3927be81 100644 --- a/cmake/modules/FindPyQt5.cmake +++ b/cmake/modules/FindPyQt5.cmake @@ -1,54 +1,59 @@ # Find PyQt5 # ~~~~~~~~~~ # Copyright (c) 2014, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # # PyQt5 website: http://www.riverbankcomputing.co.uk/pyqt/index.php # # Find the installed version of PyQt5. FindPyQt5 should only be called after # Python has been found. # # This file defines the following variables: # # PYQT5_VERSION - The version of PyQt5 found expressed as a 6 digit hex number # suitable for comparison as a string # # PYQT5_VERSION_STR - The version of PyQt5 as a human readable string. # # PYQT5_VERSION_TAG - The PyQt version tag using by PyQt's sip files. # # PYQT5_SIP_DIR - The directory holding the PyQt5 .sip files. # # PYQT5_SIP_FLAGS - The SIP flags used to build PyQt. IF(EXISTS PYQT5_VERSION) # Already in cache, be silent SET(PYQT5_FOUND TRUE) ELSE(EXISTS PYQT5_VERSION) FIND_FILE(_find_pyqt5_py FindPyQt5.py PATHS ${CMAKE_MODULE_PATH}) - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_PREFIX_PATH}/share/krita/pykrita" ${PYTHON_EXECUTABLE} ${_find_pyqt5_py} OUTPUT_VARIABLE pyqt5_config) + + if (WIN32) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_PREFIX_PATH}/share/krita/pykrita" ${PYTHON_EXECUTABLE} ${_find_pyqt5_py} OUTPUT_VARIABLE pyqt5_config) + else (WIN32) + EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_pyqt5_py} OUTPUT_VARIABLE pyqt5_config) + endif (WIN32) IF(pyqt5_config) STRING(REGEX REPLACE "^pyqt_version:([^\n]+).*$" "\\1" PYQT5_VERSION ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_version_str:([^\n]+).*$" "\\1" PYQT5_VERSION_STR ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_version_tag:([^\n]+).*$" "\\1" PYQT5_VERSION_TAG ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_sip_dir:([^\n]+).*$" "\\1" PYQT5_SIP_DIR ${pyqt5_config}) STRING(REGEX REPLACE ".*\npyqt_sip_flags:([^\n]+).*$" "\\1" PYQT5_SIP_FLAGS ${pyqt5_config}) SET(PYQT5_FOUND TRUE) ENDIF(pyqt5_config) IF(PYQT5_FOUND) IF(NOT PYQT5_FIND_QUIETLY) MESSAGE(STATUS "Found PyQt5 version: ${PYQT5_VERSION_STR}") ENDIF(NOT PYQT5_FIND_QUIETLY) ELSE(PYQT5_FOUND) IF(PYQT5_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find PyQt5.") ENDIF(PYQT5_FIND_REQUIRED) ENDIF(PYQT5_FOUND) ENDIF(EXISTS PYQT5_VERSION) diff --git a/cmake/modules/FindSIP.cmake b/cmake/modules/FindSIP.cmake index 4bb8b40973..444add0f41 100644 --- a/cmake/modules/FindSIP.cmake +++ b/cmake/modules/FindSIP.cmake @@ -1,68 +1,72 @@ # Find SIP # ~~~~~~~~ # # SIP website: http://www.riverbankcomputing.co.uk/sip/index.php # # Find the installed version of SIP. FindSIP should be called after Python # has been found. # # This file defines the following variables: # # SIP_VERSION - The version of SIP found expressed as a 6 digit hex number # suitable for comparison as a string. # # SIP_VERSION_STR - The version of SIP found as a human readable string. # # SIP_EXECUTABLE - Path and filename of the SIP command line executable. # # SIP_INCLUDE_DIR - Directory holding the SIP C++ header file. # # SIP_DEFAULT_SIP_DIR - Default directory where .sip files should be installed # into. # Copyright (c) 2007, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. IF(SIP_VERSION) # Already in cache, be silent SET(SIP_FOUND TRUE) ELSE(SIP_VERSION) FIND_FILE(_find_sip_py FindSIP.py PATHS ${CMAKE_MODULE_PATH}) - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_PREFIX_PATH}/share/krita/pykrita" ${PYTHON_EXECUTABLE} ${_find_sip_py} OUTPUT_VARIABLE sip_config) + if (WIN32) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_PREFIX_PATH}/share/krita/pykrita" ${PYTHON_EXECUTABLE} ${_find_sip_py} OUTPUT_VARIABLE sip_config) + else (WIN32) + EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_sip_py} OUTPUT_VARIABLE sip_config) + endif (WIN32) IF(sip_config) STRING(REGEX REPLACE "^sip_version:([^\n]+).*$" "\\1" SIP_VERSION ${sip_config}) STRING(REGEX REPLACE ".*\nsip_version_str:([^\n]+).*$" "\\1" SIP_VERSION_STR ${sip_config}) STRING(REGEX REPLACE ".*\nsip_bin:([^\n]+).*$" "\\1" SIP_EXECUTABLE ${sip_config}) IF(NOT SIP_DEFAULT_SIP_DIR) STRING(REGEX REPLACE ".*\ndefault_sip_dir:([^\n]+).*$" "\\1" SIP_DEFAULT_SIP_DIR ${sip_config}) ENDIF(NOT SIP_DEFAULT_SIP_DIR) STRING(REGEX REPLACE ".*\nsip_inc_dir:([^\n]+).*$" "\\1" SIP_INCLUDE_DIR ${sip_config}) FILE(TO_CMAKE_PATH ${SIP_DEFAULT_SIP_DIR} SIP_DEFAULT_SIP_DIR) FILE(TO_CMAKE_PATH ${SIP_INCLUDE_DIR} SIP_INCLUDE_DIR) if (WIN32) set(SIP_EXECUTABLE ${SIP_EXECUTABLE}.exe) endif() IF(EXISTS ${SIP_EXECUTABLE}) SET(SIP_FOUND TRUE) ELSE() MESSAGE(STATUS "Found SIP configuration but the sip executable could not be found.") ENDIF() ENDIF(sip_config) IF(SIP_FOUND) IF(NOT SIP_FIND_QUIETLY) MESSAGE(STATUS "Found SIP version: ${SIP_VERSION_STR}") ENDIF(NOT SIP_FIND_QUIETLY) ELSE(SIP_FOUND) IF(SIP_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find SIP") ENDIF(SIP_FIND_REQUIRED) ENDIF(SIP_FOUND) ENDIF(SIP_VERSION) diff --git a/krita/data/CMakeLists.txt b/krita/data/CMakeLists.txt index ade756f352..b1b313b14e 100644 --- a/krita/data/CMakeLists.txt +++ b/krita/data/CMakeLists.txt @@ -1,23 +1,24 @@ add_subdirectory( actions ) add_subdirectory( brushes ) add_subdirectory( bundles ) add_subdirectory( patterns ) add_subdirectory( gradients ) add_subdirectory( profiles ) add_subdirectory( templates ) add_subdirectory( workspaces ) add_subdirectory( themes ) add_subdirectory( predefined_image_sizes ) add_subdirectory( input ) add_subdirectory( shortcuts ) add_subdirectory( paintoppresets ) add_subdirectory( palettes ) add_subdirectory( symbols ) +add_subdirectory( preset_icons ) ########### install files ############### install( FILES kritarc DESTINATION ${CONFIG_INSTALL_DIR} ) diff --git a/krita/data/preset_icons/CMakeLists.txt b/krita/data/preset_icons/CMakeLists.txt new file mode 100644 index 0000000000..cbd35188f0 --- /dev/null +++ b/krita/data/preset_icons/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(tool_icons) +add_subdirectory(emblem_icons) +########### install files ############### + +install(FILES +background.png + DESTINATION ${DATA_INSTALL_DIR}/krita/preset_icons) + diff --git a/krita/data/preset_icons/background.png b/krita/data/preset_icons/background.png new file mode 100644 index 0000000000..e52baae2e2 Binary files /dev/null and b/krita/data/preset_icons/background.png differ diff --git a/krita/data/preset_icons/emblem_icons/CMakeLists.txt b/krita/data/preset_icons/emblem_icons/CMakeLists.txt new file mode 100644 index 0000000000..2c32858e9b --- /dev/null +++ b/krita/data/preset_icons/emblem_icons/CMakeLists.txt @@ -0,0 +1,6 @@ +########### install files ############### + +install(FILES +emblem_angle_30.png + DESTINATION ${DATA_INSTALL_DIR}/krita/preset_icons/emblem_icons) + diff --git a/krita/data/preset_icons/emblem_icons/emblem_angle_30.png b/krita/data/preset_icons/emblem_icons/emblem_angle_30.png new file mode 100644 index 0000000000..b984492a52 Binary files /dev/null and b/krita/data/preset_icons/emblem_icons/emblem_angle_30.png differ diff --git a/krita/data/preset_icons/tool_icons/CMakeLists.txt b/krita/data/preset_icons/tool_icons/CMakeLists.txt new file mode 100644 index 0000000000..e7df87dc10 --- /dev/null +++ b/krita/data/preset_icons/tool_icons/CMakeLists.txt @@ -0,0 +1,10 @@ +########### install files ############### + +install(FILES +inking_brush_a.png +inking_brush_blurry_a.png +inking_dynamic_pen_a.png +inking_dynamic_pen_small_a.png +inking_dynamic_pen_though_a.png + DESTINATION ${DATA_INSTALL_DIR}/krita/preset_icons/tool_icons) + diff --git a/krita/data/preset_icons/tool_icons/inking_brush_a.png b/krita/data/preset_icons/tool_icons/inking_brush_a.png new file mode 100644 index 0000000000..c9d3d69686 Binary files /dev/null and b/krita/data/preset_icons/tool_icons/inking_brush_a.png differ diff --git a/krita/data/preset_icons/tool_icons/inking_brush_blurry_a.png b/krita/data/preset_icons/tool_icons/inking_brush_blurry_a.png new file mode 100644 index 0000000000..9e483b453a Binary files /dev/null and b/krita/data/preset_icons/tool_icons/inking_brush_blurry_a.png differ diff --git a/krita/data/preset_icons/tool_icons/inking_dynamic_pen_a.png b/krita/data/preset_icons/tool_icons/inking_dynamic_pen_a.png new file mode 100644 index 0000000000..f7df7a2d81 Binary files /dev/null and b/krita/data/preset_icons/tool_icons/inking_dynamic_pen_a.png differ diff --git a/krita/data/preset_icons/tool_icons/inking_dynamic_pen_small_a.png b/krita/data/preset_icons/tool_icons/inking_dynamic_pen_small_a.png new file mode 100644 index 0000000000..4b2f908e02 Binary files /dev/null and b/krita/data/preset_icons/tool_icons/inking_dynamic_pen_small_a.png differ diff --git a/krita/data/preset_icons/tool_icons/inking_dynamic_pen_though_a.png b/krita/data/preset_icons/tool_icons/inking_dynamic_pen_though_a.png new file mode 100644 index 0000000000..14aad3b39a Binary files /dev/null and b/krita/data/preset_icons/tool_icons/inking_dynamic_pen_though_a.png differ diff --git a/krita/pics/app/1024-apps-calligrakrita.png b/krita/pics/app/1024-apps-calligrakrita.png index c91c0a0e23..8c8388c05e 100644 Binary files a/krita/pics/app/1024-apps-calligrakrita.png and b/krita/pics/app/1024-apps-calligrakrita.png differ diff --git a/krita/pics/app/128-apps-calligrakrita.png b/krita/pics/app/128-apps-calligrakrita.png index ec2dfe98c8..4de5c41e85 100644 Binary files a/krita/pics/app/128-apps-calligrakrita.png and b/krita/pics/app/128-apps-calligrakrita.png differ diff --git a/krita/pics/app/16-apps-calligrakrita.png b/krita/pics/app/16-apps-calligrakrita.png index bd7094980e..509edbddf3 100644 Binary files a/krita/pics/app/16-apps-calligrakrita.png and b/krita/pics/app/16-apps-calligrakrita.png differ diff --git a/krita/pics/app/22-apps-calligrakrita.png b/krita/pics/app/22-apps-calligrakrita.png index 96204235c2..598b25ac80 100644 Binary files a/krita/pics/app/22-apps-calligrakrita.png and b/krita/pics/app/22-apps-calligrakrita.png differ diff --git a/krita/pics/app/256-apps-calligrakrita.png b/krita/pics/app/256-apps-calligrakrita.png index 4ac9d16d5c..eded0ce78f 100644 Binary files a/krita/pics/app/256-apps-calligrakrita.png and b/krita/pics/app/256-apps-calligrakrita.png differ diff --git a/krita/pics/app/32-apps-calligrakrita.png b/krita/pics/app/32-apps-calligrakrita.png index a5d324e4bd..6f0d408bfc 100644 Binary files a/krita/pics/app/32-apps-calligrakrita.png and b/krita/pics/app/32-apps-calligrakrita.png differ diff --git a/krita/pics/app/48-apps-calligrakrita.png b/krita/pics/app/48-apps-calligrakrita.png index 44751016ec..6ca751b520 100644 Binary files a/krita/pics/app/48-apps-calligrakrita.png and b/krita/pics/app/48-apps-calligrakrita.png differ diff --git a/krita/pics/app/512-apps-calligrakrita.png b/krita/pics/app/512-apps-calligrakrita.png index 5b3d23d95f..3c486e1625 100644 Binary files a/krita/pics/app/512-apps-calligrakrita.png and b/krita/pics/app/512-apps-calligrakrita.png differ diff --git a/krita/pics/app/64-apps-calligrakrita.png b/krita/pics/app/64-apps-calligrakrita.png index f8e427eecc..6ef8772a4c 100644 Binary files a/krita/pics/app/64-apps-calligrakrita.png and b/krita/pics/app/64-apps-calligrakrita.png differ diff --git a/krita/pics/app/krita.ico b/krita/pics/app/krita.ico index 01950d7c99..939d4c6f92 100644 Binary files a/krita/pics/app/krita.ico and b/krita/pics/app/krita.ico differ diff --git a/krita/pics/app/sc-apps-calligrakrita.svgz b/krita/pics/app/sc-apps-calligrakrita.svgz index a2ad31fcfd..1b751cba47 100644 Binary files a/krita/pics/app/sc-apps-calligrakrita.svgz and b/krita/pics/app/sc-apps-calligrakrita.svgz differ diff --git a/krita/pics/mimetypes/1024-mimetypes-application-x-krita.png b/krita/pics/mimetypes/1024-mimetypes-application-x-krita.png index 1690f7a6e7..22af0dc0ce 100644 Binary files a/krita/pics/mimetypes/1024-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/1024-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/128-mimetypes-application-x-krita.png b/krita/pics/mimetypes/128-mimetypes-application-x-krita.png index 07479934d1..630ca79215 100644 Binary files a/krita/pics/mimetypes/128-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/128-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/16-mimetypes-application-x-krita.png b/krita/pics/mimetypes/16-mimetypes-application-x-krita.png index 53e73f45dc..86b0078eca 100644 Binary files a/krita/pics/mimetypes/16-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/16-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/22-mimetypes-application-x-krita.png b/krita/pics/mimetypes/22-mimetypes-application-x-krita.png index 2ee2bfce3a..c2581dcfec 100644 Binary files a/krita/pics/mimetypes/22-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/22-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/256-mimetypes-application-x-krita.png b/krita/pics/mimetypes/256-mimetypes-application-x-krita.png index 215b8a9e81..6e4071520e 100644 Binary files a/krita/pics/mimetypes/256-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/256-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/32-mimetypes-application-x-krita.png b/krita/pics/mimetypes/32-mimetypes-application-x-krita.png index 0960a9ea11..c6d1f0eb0a 100644 Binary files a/krita/pics/mimetypes/32-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/32-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/48-mimetypes-application-x-krita.png b/krita/pics/mimetypes/48-mimetypes-application-x-krita.png index 3d03c0827b..006940992a 100644 Binary files a/krita/pics/mimetypes/48-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/48-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/512-mimetypes-application-x-krita.png b/krita/pics/mimetypes/512-mimetypes-application-x-krita.png index 6d1037999f..3ce72f57c9 100644 Binary files a/krita/pics/mimetypes/512-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/512-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/64-mimetypes-application-x-krita.png b/krita/pics/mimetypes/64-mimetypes-application-x-krita.png index 3c7b37c9a4..9b8d260508 100644 Binary files a/krita/pics/mimetypes/64-mimetypes-application-x-krita.png and b/krita/pics/mimetypes/64-mimetypes-application-x-krita.png differ diff --git a/krita/pics/mimetypes/application-x-krita-16.svg b/krita/pics/mimetypes/application-x-krita-16.svg index b5aaeee97e..ddb36e6202 100644 --- a/krita/pics/mimetypes/application-x-krita-16.svg +++ b/krita/pics/mimetypes/application-x-krita-16.svg @@ -1,1628 +1,1652 @@ + + + + + + - + + + + + + + x1="1327.5938" + y1="230.86234" + x2="1324.9103" + y2="89.862335" + gradientTransform="matrix(0.0111212,0.99993816,0.99993816,-0.0111212,1077.8896,-1093.5223)" /> + + id="guide4075" + inkscape:locked="false" /> + id="guide4077" + inkscape:locked="false" /> + id="guide4079" + inkscape:locked="false" /> + id="guide4081" + inkscape:locked="false" /> image/svg+xml + transform="matrix(0.04385685,0,0,0.04385685,10.164201,8.8850378)" + id="g4426" + inkscape:label="icon"> - - - - - - - - + inkscape:label="border_shadow" + d="m 896.99215,540.84321 a 384,384 0 1 1 -768,0 384,384 0 1 1 768,0 z" + id="path1280" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#000000;stroke-width:44.79999924;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.47619048;marker:none;enable-background:accumulate" + inkscape:connector-curvature="0" /> - - - - - + clip-path="url(#clipPath1447)" + inkscape:label="border" + style="display:inline" + id="layer1-5" + transform="translate(0,960)"> + + + + + + style="display:inline" + transform="matrix(3.50294,0,0,3.50294,-3949.2328,-128.64558)" + id="g1401" + inkscape:label="brush_krita_4"> + sodipodi:nodetypes="czzc" + inkscape:path-effect="#path-effect1301" + inkscape:original-d="m 1289.3964,190.20007 c -17.7948,20.19591 -54.5955,-7.95181 -69.8766,-25.44427 -15.2811,-17.49248 -72.3403,-76.489933 -76.5109,-80.316254 -4.1706,-3.826321 -7.492,-8.551684 -3.8037,-13.253077" + inkscape:label="holder" /> + sodipodi:nodetypes="ccscc" + inkscape:label="stroke" /> + + - - - - - - - diff --git a/krita/pics/mimetypes/application-x-krita-22.svg b/krita/pics/mimetypes/application-x-krita-22.svg index 0881839f7f..4046c6f822 100644 --- a/krita/pics/mimetypes/application-x-krita-22.svg +++ b/krita/pics/mimetypes/application-x-krita-22.svg @@ -1,1727 +1,1769 @@ + inkscape:version="0.92.2 (unknown)" + sodipodi:docname="application-x-krita-22.svg" + inkscape:export-filename="/home/wolthera/krita/src/krita/pics/mimetypes/22-mimetypes-application-x-krita.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + + + + + + + gradientTransform="rotate(-5.8887665,1223.2651,99.834013)" + x1="1142.5884" + y1="62.713318" + x2="1280.8954" + y2="200.87404" /> + + + + + + x1="1327.5938" + y1="230.86234" + x2="1324.9103" + y2="89.862335" + gradientTransform="matrix(0.0111212,0.99993816,0.99993816,-0.0111212,1077.8896,-1093.5223)" /> + + inkscape:object-nodes="true" + inkscape:snap-global="false"> + id="guide4138" + inkscape:locked="false" /> + id="guide4140" + inkscape:locked="false" /> + id="guide4142" + inkscape:locked="false" /> + id="guide4144" + inkscape:locked="false" /> + id="guide4146" + inkscape:locked="false" /> + id="guide4148" + inkscape:locked="false" /> + id="guide4150" + inkscape:locked="false" /> + id="guide4152" + inkscape:locked="false" /> + id="guide4181" + inkscape:locked="false" /> + id="guide4183" + inkscape:locked="false" /> + id="guide4185" + inkscape:locked="false" /> + id="guide4195" + inkscape:locked="false" /> + id="guide4197" + inkscape:locked="false" /> + id="guide4199" + inkscape:locked="false" /> + id="guide4223" + inkscape:locked="false" /> + id="guide4227" + inkscape:locked="false" /> + id="guide4233" + inkscape:locked="false" /> + id="guide4237" + inkscape:locked="false" /> + id="guide4239" + inkscape:locked="false" /> image/svg+xml - + + id="g4714" + style="display:inline" + inkscape:label="base"> + + + - - - + clip-path="url(#clipPath1447)" + inkscape:label="border" + style="display:inline" + id="layer1-5" + transform="translate(0,960)"> - - - - - - - - - - - - - - - - + width="256" + y="0" + x="0" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAABHNCSVQICAgIfAhkiAAAIABJREFU eJztvXusJVl15vnbJ7OysrIyq4aa6qni4XrYoOEhVLhbMn7I9tDdJfcAAuNRY2NLM7bAMmOaaWS1 2xaDRhpPtyWs1giLNmo0NQ0aqWkMmsaNaMDCgNwSMqXRqAuhKpDapqikHtS4nEVWZt578z5izx8R +8R+rLX3jjhxzr03byzVqYi99to74pwb37e+vSLOSWOxzDbbbCfTFod9ArPNNtvh2UwAs812gm0m gNlmO8E2E8Bss51gmwlgttlOsM0EMNtsJ9hmAphtthNsMwHMNtsJtpkAZpvtBNtMALPNdoJtJoDZ ZjvBNhPAbLOdYJsJYLbZTrDNBDDbbCfYZgKYbbYTbDMBzDbbCbaZAGab7QTbTACzzXaCbSaA2WY7 wTYTwGyznWCbCWC22U6wzQQw22wn2GYCmG22E2wzAcw22wm2mQBmm+0E20wAs812gm0mgNlmO8E2 E8Bss51gmwlgttlOsM0EMNtsJ9hmAphtthNsp7llwtnshHMdt7lXmWPIWC32Zi4APwLcC9wDvAS4 G7ize90OXADOAWeBm4BT3egDYA/YAbaAK8Bl4Lnu9X3gaeAi8ATwV1zninqOZsD7GRI75dgp5zjG c5vm7u6KmuIiXCW2Jm7yGCU4N8eYviH+gs+8yJwFHvBerwZeCdzFei+5+IyeBb4NPAZ8w73s83Yn iJTOqNY3xj+yzxT6q+efOmZI3IhYc3BfATFqdyVxrNI/Jdi0vlXBKflrxla2F3/LvBj4WeCngJ8A XgucUc7ksG0X+CbwF8DXgD+3f908s+yNL86h7dqY2rFjYofOs2rfgH41LDPeHPzXTerdBPCm8k+S XW0+bkJAZ/eBxd2LW4A3AD8HPEib3TeV2ac2S6sSvgT8KfDV5plmOwtibX9MuyLG1I7TfFPEjvFP NMbsv7aZDrxrAbQdJZVV39Tgru6zat/iJafuAN4CvA34+7Tr9GOLetkstPWFPwM+A3z24KnmUg78 wfuvJYZ1qIpVCCJ+H0PGDvWPIASz97cVAphw3bp0rBPINTHZtnB+Y4BeuX/qZafOAT8P/DJtpq+S 9ceDFKoKP7u0yuATwJ8cPHmwBdSpgTGKQehTCWZMe2xMxjeaOAYQgdn9MW8JMAqMmVrAFKCtiYll /Fgg52Kr4vQsj4XTP3Qa4PXAu4C3A7exoh0NQqgCfM5eAD4FPAQ8vP+9/dZbow7GEMAQcGf6xOXD uogiE5MlstLY6z9ZSwBrBnrSHrguX7NMH71v4aZ7Tt8C/ArwHuB1rMk2SwYrg16zR4A/Av7N/sW9 baB/Y1Opg00tL+L19oZVhkiScczOz0hLgEq5vqrkjn2Tg1whrUlBHh3D67/p3pvuAt4L/AbtvfiN gXQ9x1kb6KXjPAd8FPjw3nf3nhWB6F/oE5JDYNoxKsdVtWsy+UTkkYTtvKGhWj4PbhfW1aX2kLkn W69XzFvov+nem+4Bfgf4NSg/arVOUphm7nUCvzj3NvAx4IN73927WK0GVlk+jFxWVAN4BJjXRQ5m +8GDiUA/9Rq8QB6Dwb86sMPY9P2eue/My4AP0AJ/9L36qQlhtfmmBv/o+XZpieCf7T6++6QEdFMB /mR/KFEMnX/ofBXzBLurLku2/sFBuzc4M08J+IHLAXVfOafB+5nzEXxn7jvzIuD9wD+ifcx2MhBP SQbD5poS+FPNZaF9XPlfAr+/+/ju81kgSzJ7ouXB4Hm08ynFDZgzuxRS2ubamyoIwEY7YwGfI47q OSYAeWmOoq9t3HzfzTcBvwn8L8AdFGxVME9BBnVzTAHYVecojr8E/B7wkeuPX9/LAnxA5h+lIrQ5 4pja8bm4yr7iMqdrm6tvqVkCrJLtB5LGpGAflsmHqICb77/57xn4MPAqRthhkkF+7CrAXTvoJfsW 8N7rj+98GRie/WuVgxZbub+KYiiqhJwqKC0BrrztQAHRCrK8Zr0t7g8A/FiiWEHqY+Hs/TffBXwI +EWij3N9oJx+nDx2LIA3PS4Za4E/Bt53/fGdZ4HhAB9YH1jujh1XGafWJXJxSp+kCsyV/+6gb60E /jWM0YgpB1JtXyKJAQRx9v6zAL8K/O/Ai6iwTYJ69WONAeOmxlSPex74LeDjO493X0pcIfuPLSoW 5fdQhTH1GL/9wtv9GsAYyT10zATxU63xK0nl7P1nX0L7lNp/6/UOBt2648ePGQrKTQB/pfgvAO/a +c7O0yWwJ2AtgL6aFFaQ/4PjK+OkMebyOw5CIFdn8CFAHrokGJrlh0h55VyU+Ft++Ow/BP4VhSLf USOD+ng7IH6jIF41/hLw7u3vbH9aBYgC5mK8tz8IrCPUwZD4ocVHA5gf/MoBCShgogw+BPgRiEux RRWQOV81vp/3lvtvOUdb5Ps1hGuiZMcjNvyj5+OHgPPIxFraZwfeu/2d7a0EiGOXAiXAVqqDIZl7 EiIQSME8/9/vjwepul8L/Mr5SsceqgZyhGPh3A/f8mraL6e8hozVgnEdAF99zhRMtXG61cZufM5H gbdvf2frMUBVACIp1GZ9bb1duWSoVRK1RFBLLubSr3bfvNoU8IfMV529B8ZlYs7df8vbgf8TOA/T A/JoxOlgqY0L7VjEXQXeufWdrU9l1/ExIIeqgzFx/nGV8esigtPW/13gKbK57x8l820BxEPiuhgj 9YUx53743MLA7wP/1BuRXGIa4MbE5cBrK2KGxpnkDMbHhUefIm7txzsPfPLcy8/9beD9W49fa78G qywHbE7ae33WRDFj4oTjqsB1cdSpBls4nnnu13NLAAHU/n51Nq9RBpUEMnQJUKEEbr3/3Hng3wJv 9qKrsmspZoo5pp3LThBz1Mhh8ByfA96x9Z1rV8dk/polQAnAxWOV5vHPJ3MeybFi/3O/ES0BBmX+ AYAs9a+U7SvmUGLO3X/upcDnTOF7+keJDMYfx04QMzkYR8asPMcjYN+89fjWU0B+XZ4BV4kwcqqg llCqCUc7j8y5mL/+H7UaQAlUpcw/IbDFscIxaub3trfef+41wOdpf0d/aZsA8rr705gUDOkcuZhN gHYT5BL0XwTeeO3xa4+KWbQGyENBPDVR+P2l8YLf/H/v2e8dOeAF+wXgTg3sJC6nPOqOfet9t/44 8B+AO9YJxsMmgrZfB0UJ4KXxubGb6V957kvAm649fu3rg7J6jfRfJdvXEsWQfmF+8+x7BQLIgmig MpDGDx1bk9EL2d7vP3/frX8X+Pd0lX7fjirYV5s7v6bPPwmYG3uowJ1y7FXgrdcev/qVItArVUFR MUh90nEHjk36C+dlvv+PYwJYQfpnM/sKqiCrCHKEk/adv+/WfwD8O7xf6hkLvHX0TT+vreofPnYs ANcF7JX7toFfuPbdq18EBkn8XNbO1RFWWRZkj6mMkfrNM7+1z0oA98eNyuzDAFw9p+DvwP8Z0/1o h2RHiQxW70sv/HCcre7r+w8NoJvo2wH7No0ESqog2zc0o6+iFPzjlQjg6X8yBQEMyNCl+YrjclJf n+/Cvbf+XdrbP8Fv9E0NwKNCBDlZnwNyvk+b86gAeJL5toE3X/3ula8Myt4aSXj7Y9VAVd+Q+fz9 p/5pLQGMyPBZ2a/1VWb2oC8/5sK953+c9h+hKD7dNyWwD3dMWdprfcPGTAnGIzPmKvDg1e9e+Xox 69dk50o1oGb0zHJgjEIICODJ391PgQQDCKAiiyd9GmBXyPoK+Vy49/xrgP9oMt/mGwrGqfzrm6te 2mt95TFTAm8Tcw0+9iWwP3P1iSuPAnkQTqgGNECPUQ81S4T2UWA1S3dbI4DMSHG+zyp9NvQt+xSQ GyvERvME295/4b7zL6W9z39H/Gf2Pxd7SH6/b1W/65Nkej9G7pMArflRfTec/w7g8+fvv/CTV594 4Smgf0w3Alzij/okUIuP6Jruc6+ZR5hTnKsQd5oaAlB9GdBK8dbKxOHmGQJyP3a57f0X7jt/Hvic 9R7ymQLE6wLwuv2uT/bLsl/2W2GeIwHYgn/UHPcAnzt//4WfvvrElatAClINvGNA7fy5eaT4jDop +U5b43UMyvAZn9gnKIIastBAHsd67Qs/dH5h22f7X+e/7ylAfxSJwyRREtjtBH5p/k0rhE3PYV8H /Nvz951/69WLVxo169eAdwyoc0AeoAY0n/xtwFzWLxKBAOgkswuxA0EuZX23tQt+33hf7JHAOQTI U4B7SoKQY9Ns3c9hE78E3ry/FkgrA25A7MbO4c3A79uF/d0YrBKwJyGHKEaMlWKUcVqf+e7/6n8X QMnGcfYGsgW+hECk8drxMseq8N92z/m3A58kwkkMjlV9R2+8VWJ1vzyvLPvL8x4qQNc03saNX7py 8YVPSRlbLeoJfnG84EtiM+Ozx8oczzz+v0UEIGZ/CewR+CTfkPEl8FfE3vay868GHsZ7xHdq4B81 IpEytuaXwCqRSDk2nvcw1cFGfVeB11/53guP1QIbBpBA5djAP3R8dPx+CSBl6FgDezK7vs8KMVaI 1chDmt8mx7vtZefP2fZnvIJ7/f6fM/b5n8lh+VafU87wVvXZxIfiT326EtDbQ3yrjp/SJ8acBz51 4d4LP3bley9sSdJfy+Tx1pKCOPBpc+bmHdHX3wZ0R9HW2rGvtq9EGrlYsRCYgh9rsQs+DLxmCPBz MZvw1YN2jE+q1mvHliv7oc8Kx4l9awfgIfiSmNeA/TAL+84qwCrALm6nihV8PvGYv/yg8CSg/56l LI7iS5YFFTHa/APibnvZhX9o2n8ZJriG02yV+mpiphw37VzyOl6S5BJA633SfClQYgJJbdPAX9s4 C/zilScvfxpkcEtKYOW4gTHJ/ILP/Oc/EL4NuCmwq8esn+u2l154iYFv4j3pNxXoNg3oYeOsEmOV cfXFvfJ8sU9ux/F535EDeSnmEvDaK09efnoVwJcAum5SCGsAxtv67zXpkwAaxZTmW46x5bHKUuC2 l14AeMhG4Pf/bPGha2K0MVJMblwuxvlqYlYfZ4VxNT4b9EjqIIzZpApYV0z1mDuAhy7cc9sbrzx5 ebT0L9YNcn3aXAPmKxcBawhh7WCPt8t5ftV6/1yXBOwxQJ+KHNZHFlaJkZ4FsMLcqW9MwS+M0clD Giv71grYicdYaK+9X2VhPw4VYD4EUijFmG9/qPBtQOdLAO19IGJMZozarpmnbd/+0gt30f7T0C+K L9T0wk19q7Y3NUfqS4EpAS4GZtxeLSY+Vq69KeCvY46qOZ8HXnXlqcvLf5U4Bm+pDSkpjFk+jHnm QHgU2OhZ2H//tWqgNDbYSoQQbbv5rOFDdOBfJYOvQxWsQxGU2vIYq44ZH5PKfEmd1KmATYB27cd4 EdgPYew7AqCtqAJGZfuKsQnRPPbh6N8GjLMu1GfqIGa6bB+3b3/Jhb8HfMlE1+vUWf6w26mvnP31 rC755Bh9THwcvR3OcxhA32jbgn3wytOXvwyMUgFjVMGgObTjPvpHB4yS8yPBO+gYAonc/uILNwHf MPAq762tBLbDBnp9uwawsi+V6f18NW2JGPS2TBoc+fZKc30L7ANXnrm8B2XwTkESYnvIGKTfAzDQ ViFs2kaKXdfWim274DcNvMr/6F1YTdu9d1sRWzN2c20r9Fsx3vl8QNqkPy4E5tuh9cc1Sdt6UTkV cFyAXR37KuA3MfYPMd1n4QEvbm9iW6UQvvmvDrr3EQF87Rl+ePv2uy+8CPhLk7nnP1VmP1oKIs7M zmoy/WqZP23n5pczf74oeFhAX8txLoF9+ZVnLj+/tgw/oUIwJnkUmBB0QzJ2PHbs1kJ6u7Bt2wXv N96v+5SyN5m+sapg1djatpbN89k/vT2Ytodl/jiL68pCA0jN/tD2YcRWzXMH8H4W9reB5HZe0J4q 26841nzjoQMPgL4KyGTkJHbVdnQsae1/94WXGfjPeD/pXZuFN903bayewVdXA9Nlfp8UyuOPMphX 7tsBXnHl+z94EpBrAUqWLhb9atvC3Nqx+tuASWY3hOt+EwJziDoYs4181vABIvDXZuGavlxmH9o3 JlbukzN6nZLIZeu0HX+rcGjmb9s2iEjj15p9J+xbaY6zwAcw9t1i5p0q8xe2pTsGbmv+08cEBbB8 LxkVkLRXyPhau9u//a4L93TZ/4x//s60DDp13LrnSNtWiI2zdBpXUgNDsvsqmT88Tm1B8NABPEXc Lq0KuBhk3kJWXkURDJnXHxvdBejyVawCqjJ3pxDWoASs4XfowB9bLtvXxE2lEKZXEn5mdX1xlg7j 0j6CkXJ2z8UOz/z+p9kfpxx7BEA75ZgzwO+wsO8B1pL5h9xRyJLM//t/Nawnk08z7vb/6vxdBh6n +xd9xmTkKRXC1IpAj6vJ4m1cflysIsZl+6kyf6w+SNpHEtBj5toG7r/y7A+eHZu51zEubod3AWoz uf+ec9sJFIA1vJfon/NyVpPVp1QI61AEcuaWsnqcxdv/a+PSzJvOMSTbr5L5wzPWlgKHAfy1znUL 8F6M/cBkCqA2NtOXkMH/82+a7pyVDJ3ry2bvKG7EHLf/rfO3ABcN3Bm/r6OyP/14OcvXqgE9+49V Atocq2V+E14AwfHG7686fvL958Dec+WvL29rGbqYySuyfJUaUPqU5wAMq1f8DUn9YOAcdsGvGLhz bJbfxL7/EVE5pjS+NFecrf1s3Mf5mTqeO767UJ/t/X0XbZL9FAxy5r+hwC7t3wn8CsY+tGrGj0E+ Ziv+aOjDf9xUZHDfv2JcMkae4/Y7zwP8J9P94x7ReQ/a36QiqM/00r5e9a/P3tKYWmUwLtuHoK/P /BpZpPu1cevcHz3HI2B/9OpfXx6WvTMZPzfH0LjMcwD+1lBX4S/EUXOsdmsNr8cDv2Y1mXaIbVIp 5DO4tp/L3nFGlpRAPqtr55XOrWf+cD498w+vBxwZUA+Z73VgX8/CPgyMVgD+tvYOQE1cVAQ09LJ9 ItDH28oxdsG7UGwIuIeCdYo5piOLFLx5Eon3bXQM6x0jN1dMIr3pxJEDuhS3CUBOPffo2Hdh7MOD ADshyOM44/u/9n93qFyet/XO30bvpyYuM75yzH/xX54/BzwD3OZffPF70/ZL/WNjpxgn71vBb4V4 vZAnz1NXBBy2H5JIut/Hpceu68PrG7e/cYCX+l8AXnz1uR9sJQDUgBmRQPUYwZ+LK9wG9BVBHFez FcZXjLWGnwdu8z7BQVm61C/FDsnUU2f+uoxuI/9Q6a+NHbovHc8q51SX+dO+Q83W6+i/Dfh5jP1E dXbPgHnoVgM/LGsAPugZAe5K0qBi7naKX6bCpiSFUv+qpOHvxwBJY+2KBJIjhyn2cyCWCCEfQ3Y7 dP8o9luAX2ZhPwEkAFe3A8A8hiSMAfMfP2u983QI7P43pb9y7O133HqHaeV/8Ny/n32G+tYxZki/ HmuFWCvETiX947mtsO/PLe3r8l2ep/5OQPpswGEDf6oxFtrvB7z46t/84NIgCV8J+rH+yrsAK26p j7WGt6A89z/WStlb8k3Zn/s44n6S2FgNpIW9cJxV9uO5bTBHeh76EkCfW8v8ITmN22r7mwL+yr4z wFsw9uNadle3AzP7kO1p6zurZP1IfxJDum2Hvi28YKazWiIYMmYIuGPgpv02Ox5lXycHCZzlfZ1g pLW+JvOHxKR+xK22v4qvdswUx7Vvc/+GQA7YQdYemdmr/V/9D9HlvY5lQDGm9d3+oltvAZ4Dzmmk VesbGr9pH+TkuPPHPnkpIPdr0r//ew+R+3VLgNwccUzOf6iZeoBvcPwWcOe15y9vAwHAa+4GTE4G +P8ykDvPlTI/+ZSoxrTzNQveYOAcguVUQSmL18TnfHUZXVcB6TFscR4/g0pj8v3OE++XjxXu93NI Phcd+v054pj4h0GkbQ3ISv2TgnZkvBhzDngDxn4+AKWWvfyt0rcKKQCcbqT0ZAugVrcRSVAxB/2+ NfwcE1uOHGqJYGh8nhwk8OcJQZ7H99WAPAZrTChyf0g0uTX+cLCn49I5wjGr+KaMH00AAD/Hovk8 UAXwkhIYsg2eLeisrwHEpoF6VOZXYgnHWcOD8Wn4F+i6bN1EUA9kmRBCEKb94Tzaml5XCCVCCeca lvEp9GtEMgJYE8dPRQBJ7IMYW5XlRaCvoASCbWcyAQwF+6DM38US9t1++60vtvDKeMhUlgN0LkYC 2vh4W+ErLQ9CcMbV/FrAx8Tig1EHtnQe+YxfU/Dr/RJhrAPcub6pY5LtK2+947YXX/vB5WckMJey +xAloD4n4FmFAoi3miLw3mN15u/HN4afNeIplm2TZDGWJDQwp754jtpsnxJn3pcSR9yfy/QlMA8h CTygh7FDlgJTAX/dsdYAP8vCfhJIwTlCCVRlfAUgOgG42aofCx6X+d3WGn4qcyaijQF+jRKoiR0K erc3jBBi8OfmsEqcpgrcWUlKQfelIO/fXZ54SiQRH2tK8NXEjALz2LE/ZYz9pArSgUpAHB/HKhbe BYgtB/aJMr/rswt+Qj2csl3FpiKCmpgQnM5qCCEHfp8syuDXlwFln0wcsVIoLQHCWJIx8fi0n2As gu+wgD94+xPLOoAC2lWVQLUCaJSO5aBiLcDP/JJKyGd+LNx2+7mzFl6bOZO12RgiGKsM8jE6wNNx +tJBy8J5ssj7+vlkaV+/BOjfZ0nq1y0FpgDxmDErb1977kUXzm5dvrwjAb2kBJLsniGKgBQEyy8B 3GAx43uXVUklFOaxhgcsnFk1q69iQ4ggHjMk+/sg02NKSwGtr0YVxJm71pcHd11cLTGk+23/lOBd ZezK2zNgH2BhH14CtKAEStm9OI9iZQJwk/jAxupgl9AQKwd/UgPW8IDzSpkrzm7rtBpQ146V55DA qHxsYow/vg78PahTIqlZ96fglv8epdt/ki8lBu2uQK0KOCqAL8Y84P9IiPRjIGMkv1gbyFgdAbiZ EykfAVslCG9L6msMD9SexqaJoQ7UmtWAPZwtpwZq5tP7UrLQngEIAZp+zjExDFMOoS8li/AzSVXC sQO6tn2AhYeneKtk+5Lkd9tc4c+3fA1AMi3j1xAEgq9VAK+GaDpvW2NHhRh8wMlnWZ5Dipcyt0wM miqIAe5/RilI9T679MokIKkPf45SnD+ff6wpVMChAF3re7UxTR7MNQAvKIKS1SsAdyZSxq9c62uk YU3+AaBVQL1uItCsRBDuIg/NZsaWvz+gEYRWh/DPId83DQn079L3hSDH8/fHiscedeBX9b3S3QnI gjkHcAX0tdkfhhKAm93a6uyu1gy6QRfO33KhgbuGgH4MIaxCIsMtBrbmKwM4VQX9XFrGDwkiBWNM Joh9+kNGdeO048bk0RMK0TiZIKbKytp2bYCPt3fdctv5C9tXr1wJgCuBeYBKGAJ+KD0HEFuSdjrA Z7K7HN/7mgU/4oisBvTaaW0O3GXTQB1a7JEJwvXJIJflfkgQuoLQ7vPHMQTjU4Loj5qSAMl4H/CW HvDx8iAtDubJYKrtOmMCnwH7IyzsI3Qttx0q+aV/8KMWCMNqAEvajy7pOLvnlgRRvDXciz/cP0yl L7YaIliFLHSwuMtZOpqe3cvt/DY8N40g4rnzBcB4SzBfTi1IKiMEbvwee8D74MabMZ6rVA84DOCP 8t1rTPNInOFdZjc1Pm8rfduvZMOXAM7irO5n92WM60MAf9tnDfdIYFzFV9M3JMaZnMnj2Wp8Q8wW 98uEIRf5EPu0uaYhgT4izvruPcV9NuobclfgyALf+e7J1QECYEd90g+JBNtKG0cAcVZfXlHOJ/UJ 44DG8BL/YhurAiQfUd9h2rjsn5Pm0la/1ReSl+3OKUccKYgpxmgkkJP5cYw7P7vc04miVBA8VHAX fBbgJcGtQCXru30TbaW+oTb8NqCz4EroGuKV5PpkNWANd0tARpgq7mcCX01fndmgJYFn6Bxan0YS +Xkc6GvAXirs5QqLKSi1MX1MShCuHZKBfztQBFSFb2zfaJDn+u8mvhUYZfbcl36GPvQj2fglgDsD tdDnXZbxVecphcZw5xjwr4sIVrU84GOSqMnAKXjk+XLr/SFgz8fXkUCoQGSA57K+JU8G8bGnIoB1 AD8beycLm83sRH2rPPQj2WoEsDyDFsxaoa/vS5WCNe0//22iIbXgnxr048ihLgsnPFiIz43VtzWg rxmvk9NwEgjfXxojAd2PteiEkVsKrEIKUwJf3b9T+nUgl9mN54tVQkIaI238EsCZdFVnC4Murt1p DLcPAXlpv0QOvo0D+3BLM7c7ehyjZ/90TLpfD3qt8Fe7ZfmOdBLQs35/zmkmj9shaNxxa5cCq5DC 2DkH7d/ungbMZn+fGLS4kTaBAoDs8wC4fVkFWMMFFx4rALx27b5kpdjViCAGdu+TM31oMjlIMdrW Vx/xxSmdp0QWQxRBf7ayWqh9ZiF+VqA/P20JIJGCTBgkc8q+Kftr94P2hfj7AGr29wnBbSfA7uoK wDcR6E4NEBFCu21M+28AaMCP+8YQBJX9WqzuywEt58sVB4dlfxlsEuhiXpYubmmbHkM6x3oSqMn6 /juVlwBubtRX/F7GgnlI7OC4c0kR0G2jrJ9dFqxg0ygAKAK99Xnk0BcBz2rA99va/rrVweoWXtAh qMP+nGLQsr90vNosXruNyUcHuVbk07J5qW1JP5F0CZBfErjtOsDu74+KO4uxCbiXIK8ghlVtOgIA gsszAjpE5NCZNdxEP6qoAPy+3Lja/ZLpsTL4xlialfv9FOgpYeTu9YfzjNnWF/ykcyQZV9PO+8m8 pDpD+nlMub9S303iV4KVrJ8QxAS2hiUA8ZXY9XUKISKHxnBKAnoO+OsggiEEUU8gMVjDDyVVA36f TifDsn9MLjaKrbktWCYBxDHxOt//XKR2KvHlLK+TljRnesxNA19tn4qLgMttJvtPBX6YXAGgAn35 nqO/WmNC0ON1D23X9uX2NQtjZPCtZu14KePHQPRt/JN+8TwlEojJIIxlM68dAAAgAElEQVQLCaVc /EsB7j5fK7ziTyq/BJCKhcNBPGbMiLbw46DLbY4YJrJpFYBv0tWW3B40WMOBhYVPApIaIPLl2rV9 kg0lBt3iCzfXlouCEqj1+BSs6bH0bJ8et0YRhH6JrHRSyJFB/F7qlwD6i4r9dQJf9B2waBJwZ2/3 TU0AkysAEIEeqIBlHDSGPUNaB5CAPIYI4nZtnw7+NCM5CNV+lBLY5fnTKDnTSz5Jtodz5MggHyOT AGJMPuPXgn7sdr0kMBr4zreX+2fCVGKY0NanAPw/p3hbsN02hh3T/XPgUubXiGDdxIAYZyvHxMDN tUsgb2P8LJvGWSEuni/O9hLQycSUSYAgJqcAasigft2fzlezFNgE0KWYwL+zrAFEIFfvDExs61EA kP6FFF9j2DJwRw7cOdAT+Vdpx/NJfamlAB5iUvb293OALs2T3sbTpH8YryuC8i0+n4TqFIAEXskk IMWgqo1bZ3uQfyv+NuByX/NNbGtUACAWBP0/vTE0hisS2PEix/iltu+raYd968j+NdbH54qAYZxG GrKvrAxCgGkKICSBIQogBv0q0l9WA6uDuiZmsP8Kpiln/zVidH0KwLeMCmgMlyUCmJIIJF+JHEpE 0Xvz4/ImAyvcl8eVlIH0MI+c2WNgan3Din39+BoFAKuBfgoSGEsEY/0WsJdLdwEC3xpsvQoAZBXg qYHG8FyOAGoAL/VJ/tinxaT9Njs+7NHH5SCdszQLh/OFoNMzve6TAS/5dBII2yTjdDIg6avfZsAl vBrv/a4T9EXgu9dzZtGek/rQz5rxuX4CcKZchbUEUCKCoWoh9hH5ym0r9muWxtmB+63lwW6Dba4G 4Pf780gk0M8nkYBGEBIxpGSA2lfK4D7JDFUBCHPV+iYBvns9V/ouwDqzP2xsCaCrgMbw/SEEUEsE Ur/kL/tsEiO3bdIfWgrmepPGSuAk2Ufw5bK/NGdIGj5QCcbKfSnofRKtlfX52DyBDAOr5KsBdqlf fH0/912AdWd/2KQC8MHvXWqN4ekxBFAC+zRkoIE/BXva1qrZcZ8NeuT9nC/ui4khrQGE/T64y0uA fp5YjcgSP4wPQUoCYHlL4qtVCSARxyGD3n89TbcEcG9U/NbfGu1wlgAGvO8CXFyFADZHBqmvb+dr BDkyKFuYjdt9eRlQUgG+pVldOp5PAvn1vz8mXVpoCqAO0KWM7wOyTlFItYCNgd5/XUx+EMRtN4TL zSwBlharAENjeGIqApiWDKz4dyiBvfdqFmb5frxVYmp8+a2mAsqKwO/3x4f7ujpwAA3jUgUwXPb7 7zFdYtQuAQ4F9P7rieUSgM1nf9joEgAxRTWGvzItJZgYpEeFDOK+2CfF9e3aOwiS1RJBb/WKIGxp wO8BlQK/B7CU7TUFEC6f/DhZHaTkQTJXXuYPrw2sFfTuZcH+VbwEEC++NdpmCQCIVcBO01y5+dTi WQN3r4sAakAd9luxX/L7QHIXYzx/eMSS1YK+v0jTTB32y/Hp2GHZX1//h750uVJSADpo3ZnkK/45 0Ifgb9gg4OPXswf7O1cOM/vDxpcAdFdBdyl0mwPDtw3cvWAcqP1/3lDL8qsRQtpf8pfVQSr/pTHp DK2lGT2NiePkLD42+zvQEcXWAj49L51ApOcFchm/XhWMAy/efjNyjubb8UNAY/5pr1XtdFOOmd7c G3UPA8FjBv4bdxHUEoEDfhP5c1YiiBhAfXxayEvrBDGQc2qgZDm1EGf10HzAluNt1X4p+4fHjwEv EZGerYnG9KQkr/WHgr7dNsttHuDVgB4a91j8ENBh2CEsAZw5FWA4MHzDB30tEVDor8n6znLEkIvT +lJ/CCiJJMoA1Uy+xSdZWhAcs5/L/r568GNlwIegzi0NSMbKvtJSoFkh869CAEn/N6TvAWzaDpEA WF4ljeEbEuhLRGAzfeOIIQWOA28K+jS7m2hknhByvhwR1BBCOC4kB6lvyL6U/ePbezGgJQWg+6D/ 3GVVoGX+uE9fLsRqYH3gl/oaHAFks9IG7HAJoPvzdApg18AZjQg2RQI1ikFSC3lCSPvS+WzRl47S tz5Q4zFhn6ZAStk/Bra2/ncw1TN8Shjx0iEeI/nymd8HoqwGRq/lB/Qt27tgvxEsAQ7JDpkAAGNo rN05bcw3DfwdB+aYCHJqAMFXevmFw9bs8v9jSCEeq/eFIJMIQRpdF6eP9wHcn1FraVZPY2SAh9m/ Hy+B2B+n72sFQ4gzufOUM79ECD4xSCSxGgGowHftbzbN3o4xhwt+OAoE0NmB4S8W8Hck4JfUQK0K 8EEfFw51cEtZOJT3/hIhHlNDCtrc8gjtnKR++dzBB3BqOiG48XXZvx9fXv/HhAEIsXlQ95/dUDXQ BPH1imAo+Jf7f2Hi+/+HZEeDAFoa/JqFf+SA6hNBiRRqSGBBDvRWHTdUBeCdb5rx/X7/rkJKGDrI 45n02BDkoaRPY0Oga5X+cI5S9o/HhGoO+s9CJgQ9s8f9NUuLvBpoKGfuIeDP7n8NmkPP/nBUCKC1 P7dgF2Bi8EvAj0mhRAIo/vzLJiRUSxaSWW9srj/1pmN0leBH5H2xCpCzfRprgh4rxMVjhmT9mBBi laEBmcSXH5PeDeiJoEQARYkv7C99Fpo/h6b0B9yIHRkCaCzPLAzftvCqGPxD1IBGAgi+GOTquZH7 W8VAS7N5mRhqlglxti9tc+cY++J9H+gSCcSZv/fXZP90Lo0QfMAS9Iex5Wwv9zfiGAG03qtK4iv7 DWC/be3+M4d579+3I0MAGLDwJUcA7iLQwK8pA40EcuogvzxY5ZUDWZ/tpKzf96dWzv7SMVuLi3x+ hoVY4sfgDufWgB6Py0v8XHyvBszyrNL9Gvnf9jdCf0OrBtolgL8/Dvw5XwPYL5kjkv3hsJ4E1O1P LfxPEgFI4NcIQQM8pADV1UEo9VuFIGXSOpPUgEki4thcVteOosXnyUgep4G+Nw3ofR/CfokQZBLQ yKQu2w+vDegZfRDg4+2fYo4O6k4fHBEm6uyrDWwt4JwP/Bj0Gvh9X60yyGX/8l2DuB6Qrw+kZpNW Xhn6ktcfL9cJ0mPI+7oqcO0Y9DVAz2X/lBBCgPbqKHzP8n5PGikZuTW9v/bPqwVfAeRAPoQElvtb 0Hx1lUQytR2dJUBr28CfWXhLQw/qWjWgFQt90EMIVokUDHbQskCuH2h/ZP8Cl0hCHlcmh/i4ucye G9fu66B3bQ3o2nIgnie8ddfH+fPF5yLtlzN8GuukvottQS/56sGf2y7n+TNotiv+GBuzo0YAAJ9p 4C0++H0yGKoKSoSgLQFSX5/dY8CXiUJWBr1JAHWXef7OQaoK6uaN93vw6eN9gPZtuT9UCTmCkG/9 1QA/rzDK5OATQdjfLPt1YBezvbDffEb4gxyqHbUlAMBngV0LZxzwJSWwHlWQXxaE3z7U5X/ujkLJ JGXQ96Rbi0QOUp8P9lDyS2MhBXwa579SSwmiNvv37yAt/jmwshyRB/lYZeB/W3Boto8zfwPYXWg+ K35Qh2hHUQFcAr7UwJt84MdKoEYN6CTQZ6KUEMK+EinIiqDmWQEpRrojIJtPFKFXihyyn5snrg/k lgJSe1j2J2j3dFHK+jUqIb4TEN4SdHUC6a5ALegTEvgS7bV9pOwoKgCATwBv8hVArARKNYISCeRU QNyXIwXILwEWWaBpFmf7vhUqAzmu9dSQSXhuMTglwMegA20pILVT0Pb9afaXSEHbz2f9JuNvvHli BRAvBTQiyGX+Zd8n1D/DIdpRJYA/AV4wcJsD+il60MdkIAFfIwMnf0vPEkAMtpwikG4Ztja2PiDH 6IDvzS7fu+/T+2OCktSAdjwt08tt/2jh8qKU/cMs7/r6ZUBN1m+i/Xjt30T7MREcUCf1/b7lmBfA /onyIR6qnT447DOQbQv4FPAuQwv+GNz+q7Q0iBVCTZ0AcgQwThH0xCABqlYdaNYDWwe3dqx8OwZr 70OI09spMP0zz2X/cJ0vzVOnBuJKfy34fRIYkvUbWhKwn6K9po+cHVUFAPAQHQE0tCQQLwdKakBT AdrWB/Z4RaCpAVtUA8MeNgozoQ78/Ph8u/fJy4Mwy5eVgD/X8OwvkYJcVKy5U6Apg1glxHWAIS9H GPah/N/i8OwoE8DDwCPA6zQScMogpwbC/VT+62TQml+Yk+W/XCB0YM7dTYhBF8bawjYGfTpf6svJ /9x4nVBqlUB/9Fy2j9s12b+ur1wbSLO+rB6crJfALrXtI7TX8pG0o0wAAH8E/B/Qg8yRgL8syKmB VRSBTwbafrk+UFcXWOXW4RDgp88L5DO/75eyOWgkIKmDUEXEtyP9e/357F/O+GVSyIHf9zeEpHBA CvYY9H4/fyR8mEfGzJu3ayXnodgtwEXgTgjB728XStuBtG/bxGeCrU18JvFZtd/NEWdxf1z4vICU 1dOML4+3VT4SX75NNibuH+pvRB9RvwNjOEZqp/sOsMYDr0naB8L+QRdzgOlAHu7vd/v7GPbRM76/ 5TngHtonXI+knd4/2gpgG/go8D9DL++dEphCEfSZ3gq+MMNLvtx+SQ2kfpmMU8VQc+cgXL6U5X5O /peThKQCIFYCsi/O5vESQG6H2T/O+JL0xyMcG+yHCsBf+4f7B91+Twwp6IPs/1GOMPgBzIM7R1oB ANwFPE6rBpZgdy9NEaRqwCqqQM7+vj9WCT6QQwWg76dKwPfr6309++cyf+1+2NbVQl22LyuBJvI1 UVwTxaXtfMb3+yQFcOC13f6B147396P9fZwCMOxBQgJ+m23gfuDZ6iv9EOz03tFWANB+gB8DfhNa 4Jym51hfCfgKQFMDkirQi4b5ugDRtqQGwrYVlUBcCyjXC4YSuJz5nTLqfeMSQ6oE/HV47POP3quB 3tfXLLTsX68G/Ap/rq8njbjw59cBbEcKIfDbJUJ37h/jiIMfjv4SwNkHgXcBZ6D9mE8TKgGfEGIC kO4W1CwLbAfSGPwwfGmg3zmwAahzxUOCGGkZYL14H4a63LfeXKnJRKDJ/b5fKxb6Rb12/hDgJdBL ywapLRNBugxovNj0h0FcbE8KLcjtsm6w770c+BuAXdpr9sjbcVAA0BYCPwb8BrQXoCMBXw1IqqCW DGpUgQNX77MD1UAP9rrbiLV1gt6nP2sQE4YNzi2f9Uv0Gb+08Y48/DhfDYTz5EEvxWjtGOjtNlz3 +2Pl6n9YB9jv9veAPXoSWGb/i8qHeaTsuBAAwD8D/gfgLLQAcaJLIoL46cFSobBmaYAwp7atXRqk SqC92PsvFLms7vwlhZDL/LlY31caVzZZBfhgj4+fFvr6ecLzKhFD2G4y/ZoCKC0FXBFwH8sehl1a EmgAdmiv1WNhp3ePDwE8CfxL4J84x2ngJspEINUIapYFqTKoWxKEWxv4/GxfUgLlGkH6oJG0ZPDj 63zOP4QIUhUgrfFdrARslp+VVAdI1/19pJ79Q+Dr0l+q/Id9vgJwSwFXGLxOq/qB9hp9csAHd6hm Xn6wOttv0F4E/CVwB7QX+5nu5cjgFD0RnApetnDHwJIqgvjOgdz2K/lpO7wD4Ff1w/2wsp9vS3cJ 9Gp/GKNV+fOV/yF3AvyqfhibVv3TuwFxdV/y+f70Xn/oS+/5xxX/8L6/f7/ftfe69l63v4thF8N1 DDvANobrQHMJeDnw/OgrfMN2evv4KABoP9jfAz7kHLu0a4IztCrgJsLsH98xyC0LcqpgSI3AZSdd FdQXDEMVEBYMIacO/Jhw/a/XEaZIBqEKALn4F2f9eP0vLwn0zO9n+nS9X5f9w/qAa/e3C/sCYK8A 8EgB9n6PYwR+gNPPHy8CAPgIbTHwVdAC/ALtQwI+CbglQYNeH+i34UNAuUIhSlvazxFAC/K0KFha IkBMDj2w/PlyYI7vIqS3FYcSQQr6sK9/7ykRtFtJ+of+mDQ00A+pD0j1Ao0I4jsBTv7vdSRx8C3D 1kfa5cDxsdNH8juKedsD3gt8ie66cnLel+Bu/8BrSy8A0+Uat2ZtCicQqwEK+7lt7b4ptEt+vz+2 MXk/lzf0+fzbjVY9/3Aeq/jDeNlnlTibjEvbft1B6ndHWGA4ZeH0ey1n9gxbHCcSyNeMjq59Gfhj 19iiFWD+HdncVzb8l422br/2NTQ+fjFwX2rHpKH1af1TvyQL821IBdq5tWaSPmnMUJ8Wo7Xl8YZu ofnHltNfbjXoOY6THVcCAHgf3XrrOu1zl44EJALIkUEM5FVBXf8yoh9lPwf+saCfxlKQ1pOCHlPz fmpjw7apiAnbcXxrp7Ccet5y6n2uDG05y3EigeNMAM8Cv+Ua12iJwH8kQ1MC/sVy+CSgg7gUQ+JL H+sZArIp3oNkY89F65P8cmz+Mac88Pv7HXr/Ajj1W3DqWctpLDfRq4BTwjs8enacCQDg48AXoAW5 WwrEJBDWeWUy0EggJIIwY+t9+cwebzWQa/s1Y2uApL/qK8NjiWHoeba2meVAXXsBnPoCnPq4Xd5s bknAcjNwK8fBjjsBQPsdgUvQEsAOsgoYsgzQVIJGHjUAWKW/FI84R0kN5EE+5FzGzFOKy/WX/EN9 fTuX8f32Ajh1ybJ4V/90ySns8sbzGSy3ADcL7+Ro2Y1AAE8D76b729zYBUEpA6Zqg2h8DXhWf+mq JzY9Tictbbzkl336ciAdZ5JzskHfwloW74bF0+5xsFAFtCRwHGoBNwIBAHya9gsYExcETQHU8kVf jqkZlycFqZ3zofTlQLiK5d5ranFM/g5B6pczd4kgxsR0z4J+DBafJgB+qwB6FXA8CoI3CgFA+2zA o5ArCKY/6ehfdDdWQVD25YA49XuQbOy5aH3xty7ysS5uDPCXz2E+Cov32ujh8XApcHwKgjcSAWwB bweulguC+TV9XUEwfA0lCZStBnJtv2asLVz0Y8EcW42ykRRG6Zh6nymOKX3huQx8v724ajFvt5gt /9sgaR3g+BQEbyQCAHgMeCdgcwXBEhloJGALY4YAf2y/BGgJBBpo4vFhvyz/pyKK2nHx+4zPLQ/6 8nJAIiS93fosxloW74TFYyHwW/DLRHD0C4I3GgFA+y8K/QFoBcG6f+IhJoFpCoKrr/21fY08UPwy eNb7Sq2ucJgjq9L7yfv67yqWxsHiD8B8Kv7eqLQUOE4FwRuRAADeD3xueEHQCCTQX6Strx7E414m 2taCP30OIY7Jg16X08NMkvj6ZybZEEKQ/TVqIVYKUv3A+RafA/N+G3y5O/0C+XEsCN6oBNAA7wAe kQuCRlwG+BfLuIJg+WGgurEasHUQSRd6yVcLslVfmo09l/rlgDOnFtJaiD52WVx8xGLeYVk0/i89 xERwXAuCNyoBAFwF3nwAF9dbEEwBOmy5EN5nlgAd+xDGamSQxmyiIFgirTTbSiAtAT73HoYW//xj e8uLi5bFm8FcdY/+hsAPv1R9HAuCNzIBADwFvHELLtU/ISgtA2Rg1xcEy1/6KfdPowRkf738H08U 8hdw9HHyF4xy51Pyx6RYiLsE5o1gnuqB7ysArQ5wvAqCNzoBQPtswJu24GpYEJSXATKgTUACc0Gw 5pV/f73lH1mOLUdWqV+q6FPjuwrmTRbzaPizKdIPvx3vguBJIACAr1+Ht27D9maeEFznKy3wEfUj jNN89V/llX5KZbjVkYIen+vP+8PiqhTbxW1beKvFfL0v8Pm/nNj/uuONUBA8KQQA8JVr8AvXYadf ChjluYD0W3/1BcG69e+YFxVzEvkJfPo9ch1kBvmcNf9wkOfPxSeb/GPFsr9U8+gJzcKOhV8A8xVH dKHc99s3RkHwJBEAB/DFLXjbLmxPWxDUgDBcKaBsNSLQ9mvGloFUI+NzakAvQNbUHerO08/OY+8O gMVsA28DvkgyZwz8G6cgeKIIAGALvrgDb96Dq5IK0MnA/Y6sA76sElZ7QjB+BkB/4q8GnBII4jlk fz9/zoYRRW4e+X3rx3PzSoXF3PcB/PcbfLZXgTdb+KIMfL99YxUETxwBALwAX9mFB/fhkq4C9B8Q GVcQDAlkCJg1IqjZT8klRwgpsGoJTH8PdXcvYkvHxkojtxyI32v2luAlCw9a7FL26wrgxisInkgC OACeh6/vws/sw0XpjsBmfkNwmqcK8eYjmhs1XpPgq2TzmuKgnIXrSUGPqSExdw5d+6KFn7HWfj0F ugzkG60geCIJANr1//Pw6B785D48UvcbgrmvDK/yFOBwwqDiWAjtvC9fZIvH1pNa3TP/+jFiWV9+ RkAeF8Q+Avyktc2jmDTDxwrgRi0InlgCaGgfDPoba5/ag5/ex3xOuhuQLwiaxJeqgfi7BPUEgbId uiTQngYMfTkg1Z1rjZWJo/SNxLonGdO+4Cm/zwE/3djmqfj2pg78G7MgeGIJALonA43hb6y9ug9v PYAPHoA9nr8hmK+wS+CIx8v+OpDr5yJl4/DWnjxP/i5B6dyk9wVYCx+08NbGNu3jvV72l9b8N3pB 8EQTADgSgOesbZ5t7O8ewC8ddD8qIqsAeRlQB/L0zoGUAeVtngj0/bQgls6jPwzkWy2Blc67RG6h ycTm98eAVc77qsX8UtPs/27THDQWAyZ+uCkG9Y1fEDzxBND/O7DQGHi2sZ86gNcfwKN1BUF/GRDu TwuUurU1yr7UzvlI+vIgG2513w3QjqMTQjt3RHCPAq8/aPY+1QPfnYOc7U9KQfDEEwD0S4EDDI2B 7zfNYwfwYwfwrw/AajK+/q5AfWFsbNYszUvUR9YnAT+2kAzqSEu/VejmDLf62BKBdP0W+NcWfmy/ 2XsMTOduSSAH+pNSEJwJgL4g6P+r8d9vmq2nDw7e2cAvNphL0jIgBny5IJgjiTqQu+xG0J/6UPxh Wy4mpsDU+jVw+qbfGiy/b60gmL9b0fkuWcwv7h3svvPgYG8rlvpjin83YkFwJoDOnApoaJcCDYbG GJ46OPh0A689gC/ULAPi24HSE4XTFATzlfza+RDncBdsfp7Qah8e8kkn/9xAHcGI5/YF4LX7B7uf dtne31oTgrm2+HcjFgRnAvDMFQSbbilgAduSwNMN5o0N/FoDz0vFwVgJ1C0DQqIYUwgsZXy8Y0B4 TMR5VikI6hK/JtvHW8kK2f954NcsvHHvYPfp9u304PcLf3XZ/sYvCM4E4Nk+sIf/bwf02fqpgwOe 3D/4+AG8qoFPNoS1gXFPCA6tAwypJYRAJuqP2w78w0Efx+WzehqTxuvvT5rbYDHWYj5pMa/a3b/+ 8f39XXyQ98SmrfcRfCejIDgTQGRxQdACjekuQgNP7h88+739/Xc08KCFb/Vgl5cB8jcFpysASkAs HS+tkmu+GsBLcau9Z82UOb8FPLi7t/OO3f2dZ3uJTyL9Mf55yHcCTlpBcCaAyPyCYAvqDtju4ulI 4Xv7+19uMA808L6m/UJJB/hw6/anLQiGUr/dpplSIgWSuWpJJLTyecp3DuT9mCRkAonmugS8z8ID 1/d2vpyCva9jlAp/Y4p/N0pBcCYAwXaBPdM/G2B9IqC/QL+3v7d3cW/vDxt4eQP/oml/UCLzhGDu nygvr/9BBtmQ7C/PF4JVIpwYMKF81qw262vLBrGwuGMx/8LCy3f2tv9wd29nL76vH+xXFP5OckFw JgDFXEHQLQVaFdD+oZcFwu4P/729vecv7u3+doN5RQMftbDr/35APsOnIJHjh0loeT8Pfh34OTVg CuemE1D9ezLQ8vJHwbxiZ3frt6/vbT+fAL5bqvUFv26scYpoFQVwYxYEZwJQzC8I9suBXhFYry7g /vAX93affGJ3990NvMLCRxrYdhfwsLsCwwhDB5WsHoj6EY4zvCCYUwRDCoPh1raf4UcsvGJ7d+vd 27tbTy4BTrsk8zO89HhvCNr+XOeC4EwAWdslLAiGt+pcgS9VBBd3dy8+sXv9PRbub+CfW3iuz+xT FQDL85CNl8EvZX2EOI0Y6mR/lQp4zsI/B3P/9vWt92zvbl1MAG0I2975L/uMBnr/2OHYk1QQnAkg Yw0tCRyY/sEg/0Eh6wN/KT/7i+2J3d1nn7h+/QMN3GMxv27hkfwzAqXnAXSQQ38h12X/cD+X9Uug ryOnGhUAwCMWft3CPVvXtz6wdf3aswHQlxV+yRet900MTl+prLIcKPtC4EsKICaTwykIzgRQsPZf FAqfDbA++E18safEcPH69e0nru88ZOFHLfy4hYcaeCH3ewOlzIngG5r9c+AnmWOoGkgBn1mqvGDh IYv5cQs/em3n2kNb169tt4Tqy/1wLe/W93Fcf3xd8teAXlrf1/rk2sHRKwiennS2G9R2gdMGFtaw MJbGgsH0f3ID1nbAt7YnBttCwl2ET1y/zsLahxfYh+87e/YfW/h5C79s22cKzkiAamgvjRDYBks/ t/Ox9Bpvr48xXlzo7+PjMXgxCP44t6ckkI7obBf4EvAJ4E+u7VzdWsLFmDTaeO/CxzgW/DMw1ovx +6x3ptZ7Rz6poPh9nwNws7wClmQUXhVee+GdgftrLrq27XrcVRIuBdoUcRrLAWa5FLiufaaDbSaA CtsHdjEdP8PCmPZfi3fABxoHKmOwtofiUhEExABP7OxsLbCfWMAn7jt79g6LeYvFvs1i/r7FnutB brzLg25OP2vHRBBvexCGROD2S+A3AbX4cW4vnJ0oLphxC8yfgf0M8Nkr21cvLYzLib609w/in7Xf LxFCC6n+iOFk/jmzfE8m6vULgW62sDgYkoHzOZDHb8NGMba7iqzX9vVQWxDsicHpzr4gaDjXfpQT 2EwAldaqAMPC9jd3GgzGWIwF0wF7SQQK8MF211afjZ7Y2b60sHx8gf34fbfccouFN1j4OQsPAq+0 YOKsXwJ87DdCn9/fx/QtHyZ+TDzO7zFhj7XwbTBfAvunwFdf2L6yvRTCnXx3xzXG5WaLNR4El36/ 3yw/RveuliAWsn9IVDHoLdabvT+rHvgh6Pt9l+FboPYKoCcHR9ASC+QAAAwoSURBVG3Wa/cKICQG G/UvMJyivapO015xB/Qq4CD5Kwy1mQAqzRUETxs4Zbu7ArbncttdtPJSoIORpxissZ2/u7iMBQsX t7e2DXx+gf38wlruPXfuxRZ+1sJPWfgJMK+12DMhwE10CfuXcq9EctI/BX+c83VB7+XZXYv5JvAX YL8G/PnlrSvPtGDvM70De282Sta+pPf9Nu0n8pkws/uThNLej1llOeCTSW45sHznogIIycQRwSmc CuiXAjcBN2O4FXiBVW0mgAG2C9xEvxRwf5alCvCAsySDjhgQgB8QBT1ZGO+ivri19cwC+8nuxT3n bj1r4QGLeQDsAxZebVqVcFdXjQhIod1LlYLf3/uN0CNmfQs8a+DbwGMWvkH3en7rhZ2+1NV+TgEW vfaSrFw2hzDbm/5cWr8X132Wfr7u83xMELnsj9efWw70IE8VQE8OKfD9tgN+DHQn9y0unbQFQJ8I mo4I+oKg4Tqr1gNmAhho14FTJiwILnwwK1m+32dZIAyJglBFdG2zRG/r+97WtZ0F9uGFtQ+7y2iB 5YduvfWChR8B7rVwD/ASC3cDd1rMnWBvN3DBwjkwZw32JtvdWDZwYDF7YHcMbFm4AlwGnjPt/fjv A08DF4EnDPzV31y7cqWvcXdgN54M76yX6D6oPUsTNaEiMEKM6zfhOOODHULAh0uCuSDY2kwAA809 IXiaviDY2OhP3imCNMv3f+IlGmAp//sLum0v47s5/EvZZUc37slr164ssI8ssI/42XeBbQuWMVjB 2+/9Sb+xSlyIUUhX0DmwO3KLY+M5QvDbdCJHCN5n4+Dqx4X3KuaCoLOZAEbYdcKCYAuWVhH48t4R Qw9k2xUCBdEXE4UBY71c5p43sP2lvByHXWLDz8HL1aXpjynnwFBax/1EcUvzAYwHPBOPIy3kSbf6 vHn7se7uikcMwVzdMsAjkBj88aeSXxKcrILgTAAjzBUET5m2IHhgYGG7P7sx/X4EfO3OwJIIkna/ HPDrAm4pEYhdV1G3/qXd50L/svYB7+YJwR/DJYJNJOuDHBdLfiDBeqAYojHGO5dEAvhNbxmwnM8m 55NSmUn+f5ILgjMBjLTrwGn6guCBMRgb8r++7veA3oG8NdsjwV1oHSCSukDg88HpZXobCls/S+cB H+ZHP2OnGT+V80sLMnl5WRAA3qRjgtwc30JMgN4f1URtPfvj9cd9N2ZBcCaAFcy/LeiA3z4b0CoC S/trQiYCe7/fXSw+URCqhP7edhRDmy2hnye+vF11vW1rywQZEmmWT2+uyXGK5CccnMQEKsA/hPX2 Y4D3/UL50XtnybsLzvYkFwRnAljB9kifEHTPBjjgO3ntLot43U/tcqAjlvaKDvNDiwvb/ZdmendZ WxPCogQJvZ/lDUcx55oQSt0UISFoIPfnce+blCD6c5TOAO/Mw3dKMuZkFwRnAljRXEHwlG2FWvuA kMEXeY0BUwP0pULw2gh1AUyHGS/bL7OxI4I400fFQ7QcmC8IBk/oLfsVed+9h3g+t9ODWX4OIME2 NpxE6g8glvrq9I9MhTdiQXAmgBUtfkKwFYG+CnBV7AzQ6UGZ3BJ0F73ni+eIs30v/a1XMPRzYUgc ZrnfK5aAEHzALt95BBsPmDohkBCCRAxuP7wDIBOFfy4p0FPfXBAMbSaACew64ROC7VKg+5MugWo7 JeCydXpnAML2EtCRL6gLQPhIcTdLsGzAu5w6IomJQwd8mkdjIPr9S4h5OFMJwY/xiWGpAqIBwWCr dfhnEcXPBcHYZgKYyNqlQF8QNAaM7R4V9v7klvibg/Etv/QWoF4X8EESFgljVbD0mBAeVQVBSGJ8 MMcCGdOPl+R8LPUlpRDELmOsN1Ya0SsBk/jngqBkMwFMZHvAdQynjO0eEDIdN9M9G4CnAqJ1vvTo sGsD/jcH6S7+HuxCtvceKPIv7fiy9yV/NSSMRAhdXKIGfIWQqobucEtr+/rzTIlBY4oe+Fbx9/+P FzLhyRhvH4g+NUkBHO+C4EwAE1q/FAgLggsP+O2FHQHdA7Ev9ZcXYRRvOiLAk/OBKlgiTK4B5BSA X/byK/CJxKf3+cAM4BX4+uztx6VkYYKxgFg30JYAPqjiVuyr0z8xFcZ653gXBGcCmNAa4qUANN4v CPmPCpfuDBBI/UxdALtc17PsS8lAqgGkl7wjHCn/SXChUyTxbUM3R6gWYpIIMrzfZ6QYp37CfO7G kLRioKe+uSA4E8DklhQEuz9LyP99ddvdMix9K9B28eLdgw6E1qZi1790cHMQKgCLfMln86MJY1Lw R8eJsriPPfFWYWTxufgWCv94hrkgmKsHzASwBosLgosl0AlVAL00Du8MyG0A6TbhUhV4XxjyL+OQ CHxfeNlLtwQhhIQP1kDau7MRMr8E/ngJsDQh+/uqoLcc6P3+uSA4E8CGzT0heNorCC66rwi7R4XF 5wMybZAzfnz7DzcGlvPEoB+iAHwwplJfrvz74JRu/am3ERPw2+WY1HSt4PpzS4P+PP1PL8z4xttv +yV1cLwLgjMBrMl2cF8WCr8y7G4B+qIue2cAuwRN/GiwWBcgBM7yUrH+5Z1XAK69/K4B3hICIUcG 4DcJmPv5SM7PnzNdApigTzdZDcQSP4yOj3YyC4IzAazJ/CcEF7b7s3i/INQEPyMG1isW2q6d3CmI iKGNU8iBFGz+XDkF4F/WOXUgPSHobgem4C8vC5D6orn6GX2LwR23YqCnvpNaEJwJYI3mVMBpYzlY Ar//oZCF9yCQe14g/lGQ5NeElj8qAgHE/FRMqgrCnFajAMIVcFAf6OS5TxDLGDXz5wuCofSP3h4x sDX5Hwv/OG4uCMb1gJkA1mztXYGuDkBfDnLfGXBPDLaA8P+kceW/BxxxncC/JByYvIeHtCVC6Atj U8lvo6f/pFuCsUoQMj8RLH3wC3F56e8sB3q/fy4IzgSwYfOfEDxl+4Lg8tkAwj+hez5A+0UhXLy0 HPAyu/9DIn52y4E+VgB+Vi6ukCWgx5m/8/njgiKmicfq4j40TRH0wM8tDWKqAx/UbYTx9tt+SR0c v4LgTAAbMP8JweWPdXbfFHTS3/12QAu4UsHPIwYkIhBiOp/7GnEO9O5WHkQKYNn2+kw/NgV/XFQk mNOv9GvPAvTA1WV/aLIaiCV+GJ0e9aQUBGcC2IAdED4bcAo46P6kTXfRJP/MmJThFenfPxsAYQ7u 28kSIbiAU2AHCoA4u9sgSyfPDxgBNgH4TbSciMBvhuT7OFLTDGl+7fN86DtJBcGZADZkO3QqwFsK +P+6UPDPjOGWA7b/+jCy9Acn+fvbfeFyIBSry3iPRCTZz3J8COQWzLI68MErqwSX+SW1EI/XLQS2 HjkXBMsFwZkANmjtXYG2PLMsCPq/HYCQ5b1/d6BfDvR3ApJSUEIEJTLQFUAP0v7ePsk8HokE4I1V gpL5I8D7amCc5UDv988FwZkANmyuIHjaLwhiu+8KpL8d0AJBKfgVMv6yX4wJxSOJD5YKwAgKIBjD 8iu8yZIhAXcMnajf9EuJ2PKyX49Mx8wFQb8gOBPAhm1ZEDTtLwfH/7pQ8M+M4f2YqPYV4kgxhLm0 M+/rwd1/yTwx6EUFsGz7kl+6zWcTKS89OBSogY5o0gs/eTdCr2ayGoglfhgdL0Bu7ILgTAAbNlcQ vIlWmLmC4MLQ/YJQx/PSrwob4V8bolcI0P/5RVWAL8H9rN7H+TDps3oY64PWv+SX6kCS9SaETX8e PUziMTWWVwYxuONWDPTUd6MXBGcCOASTCoIHtCDpnw9wwO7vFEgZP70lqAA/aMdZ3YEzXCLICsC/ kwDJCtn/RR8c+H0yiTI/NviFoLL8D61WGcwFQbkgOBPAIVlLAlFBEOG3Awz9D4d0NOCIYdnu5lw+ L4CyXCDvC0tgggIwqSAOiML0F32/RIjJRCGO6PMJ8/BYy4He7z+5BcGZAA7J1IKg8X47IOLy5Y+J OqBrBUCPCNqrWIJTD9RUKUSyP1jnhwW/5VLBuKyv3CFwYwzLWZwayAlyyfKyX49Mx8wFwZkADtH6 pYBXEKS//7/MB943B30iCP7ZMVIiIFEEaUy47o1UgUlBLyqAJaijPtNezv1SIJwj/q0AGYySYPct t0DQRsWwdf0mig59Ap0lccetIPj/A6YlOg0NbBuoAAAAAElFTkSuQmCC " /> + + id="rect1439" + transform="translate(0,-960)" + d="M 0.9921875,1.8535156 V 1024 H 1024 V 1.8535156 Z M 512.76172,151.57422 A 362.52667,362.52667 0 0 1 875.28711,514.10156 362.52667,362.52667 0 0 1 512.76172,876.62695 362.52667,362.52667 0 0 1 150.23438,514.10156 362.52667,362.52667 0 0 1 512.76172,151.57422 Z" + clip-path="none" + style="display:none;opacity:1;vector-effect:none;fill:#efefef;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + id="path1451" + d="m 42.705091,-831.6605 c -6.173573,0.26077 -13.159691,3.46149 -21.528718,11.66141 -15.1785058,27.30953 1.317262,35.72991 14.712011,46.00089 0,0 220.995666,213.52961 276.340086,273.16991 43.44948,46.82193 134.02368,115.22325 200.07309,107.34987 26.08233,18.34373 85.18078,59.44234 104.93713,69.18715 -0.3872,9.44375 1.5026,19.02029 4.89977,28.52785 18.5219,51.84164 87.76379,85.51914 47.45892,140.77134 79.34028,-48.07796 100.44212,-128.31216 66.82457,-185.5412 -20.9916,-35.73501 -51.53228,-47.46632 -76.95531,-38.92379 -15.22515,-17.21921 -75.78737,-67.51062 -98.08525,-85.86718 C 547.16306,-529.2633 460.93536,-595.2167 406.00757,-623.82246 333.84417,-661.40437 69.430386,-817.988 69.430386,-817.988 61.023898,-824.36173 52.99438,-832.09512 42.705091,-831.6605 Z" + style="display:inline;opacity:1;fill:#000000;fill-opacity:0.4745098;stroke:none;stroke-width:156.93171692;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + + + sodipodi:nodetypes="czzc" + inkscape:path-effect="#path-effect1301" + inkscape:original-d="m 1289.3964,190.20007 c -17.7948,20.19591 -54.5955,-7.95181 -69.8766,-25.44427 -15.2811,-17.49248 -72.3403,-76.489933 -76.5109,-80.316254 -4.1706,-3.826321 -7.492,-8.551684 -3.8037,-13.253077" + inkscape:label="holder" /> + sodipodi:nodetypes="ccscc" + inkscape:label="stroke" /> + id="path1395" + d="m 1316.9277,214.61304 c -5.8329,2.67141 -5.2021,4.60226 -10.3812,3.76933 -3.5721,-0.5745 -34.5172,-24.03135 -34.5172,-24.03135 -1.5685,-3.76412 0.313,-8.93876 3.8473,-13.03432 3.278,-4.30338 7.953,-7.21221 11.9598,-6.45429 0,0 29.3379,25.43836 30.6372,28.81501 1.8838,4.89578 -0.1357,4.67702 -1.5459,10.93562 z" + style="opacity:1;fill:#bfbfbf;fill-opacity:1;stroke:none;stroke-width:44.79999924;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + inkscape:label="neck" /> + sodipodi:nodetypes="csssc" + inkscape:label="hair" /> diff --git a/krita/pics/mimetypes/application-x-krita.svg b/krita/pics/mimetypes/application-x-krita.svg index 7cc04e6f86..a59be9309a 100644 --- a/krita/pics/mimetypes/application-x-krita.svg +++ b/krita/pics/mimetypes/application-x-krita.svg @@ -1,1762 +1,1802 @@ image/svg+xml + showguides="true" + inkscape:snap-global="false"> + id="guide3482" + inkscape:locked="false" /> + id="guide3484" + inkscape:locked="false" /> + id="guide3486" + inkscape:locked="false" /> + id="guide3488" + inkscape:locked="false" /> + id="guide3492" + inkscape:locked="false" /> + id="guide3494" + inkscape:locked="false" /> + id="guide3496" + inkscape:locked="false" /> + id="guide3498" + inkscape:locked="false" /> + id="guide4310" + inkscape:locked="false" /> + id="guide4312" + inkscape:locked="false" /> + id="guide4250" + inkscape:locked="false" /> + id="guide4252" + inkscape:locked="false" /> + id="guide4254" + inkscape:locked="false" /> + id="guide4256" + inkscape:locked="false" /> + + + + + + + gradientTransform="rotate(-5.8887665,1223.2651,99.834013)" + x1="1142.5884" + y1="62.713318" + x2="1280.8954" + y2="200.87404" /> + id="linearGradient1373"> + + + + + + - - - - - - - - - - - - - - - - + transform="matrix(0.9714402,0,0,0.9714402,15.389158,-946.88385)" + id="g4426" + inkscape:label="icon"> + + + + + + + + + + + + - - - - - - - diff --git a/libs/brush/kis_brush.cpp b/libs/brush/kis_brush.cpp index 10ba3b128b..5fc231b145 100644 --- a/libs/brush/kis_brush.cpp +++ b/libs/brush/kis_brush.cpp @@ -1,653 +1,652 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Adrian Page * Copyright (c) 2005 Bart Coppens * Copyright (c) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush.h" #include #include #include #include #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_boundary.h" #include "kis_image.h" #include "kis_iterator_ng.h" #include "kis_brush_registry.h" #include #include #include #include KisBrush::ColoringInformation::~ColoringInformation() { } KisBrush::PlainColoringInformation::PlainColoringInformation(const quint8* color) : m_color(color) { } KisBrush::PlainColoringInformation::~PlainColoringInformation() { } const quint8* KisBrush::PlainColoringInformation::color() const { return m_color; } void KisBrush::PlainColoringInformation::nextColumn() { } void KisBrush::PlainColoringInformation::nextRow() { } KisBrush::PaintDeviceColoringInformation::PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width) : m_source(source) , m_iterator(m_source->createHLineConstIteratorNG(0, 0, width)) { } KisBrush::PaintDeviceColoringInformation::~PaintDeviceColoringInformation() { } const quint8* KisBrush::PaintDeviceColoringInformation::color() const { return m_iterator->oldRawData(); } void KisBrush::PaintDeviceColoringInformation::nextColumn() { m_iterator->nextPixel(); } void KisBrush::PaintDeviceColoringInformation::nextRow() { m_iterator->nextRow(); } struct KisBrush::Private { Private() : boundary(0) , angle(0) , scale(1.0) , hasColor(false) , brushType(INVALID) , autoSpacingActive(false) , autoSpacingCoeff(1.0) , threadingAllowed(true) {} ~Private() { delete boundary; } mutable KisBoundary* boundary; qreal angle; qreal scale; bool hasColor; enumBrushType brushType; qint32 width; qint32 height; double spacing; QPointF hotSpot; mutable QSharedPointer brushPyramid; QImage brushTipImage; bool autoSpacingActive; qreal autoSpacingCoeff; bool threadingAllowed; }; KisBrush::KisBrush() : KoResource(QString()) , d(new Private) { } KisBrush::KisBrush(const QString& filename) : KoResource(filename) , d(new Private) { } KisBrush::KisBrush(const KisBrush& rhs) : KoResource(QString()) , KisShared() , d(new Private) { setBrushTipImage(rhs.brushTipImage()); d->brushType = rhs.d->brushType; d->width = rhs.d->width; d->height = rhs.d->height; d->spacing = rhs.d->spacing; d->hotSpot = rhs.d->hotSpot; d->hasColor = rhs.d->hasColor; d->angle = rhs.d->angle; d->scale = rhs.d->scale; d->autoSpacingActive = rhs.d->autoSpacingActive; d->autoSpacingCoeff = rhs.d->autoSpacingCoeff; d->threadingAllowed = rhs.d->threadingAllowed; setFilename(rhs.filename()); /** * Be careful! The pyramid is shared between two brush objects, * therefore you cannot change it, only recreate! That i sthe * reason why it is defined as const! */ d->brushPyramid = rhs.d->brushPyramid; // don't copy the boundary, it will be regenerated -- see bug 291910 } KisBrush::~KisBrush() { clearBrushPyramid(); delete d; } QImage KisBrush::brushTipImage() const { if (d->brushTipImage.isNull()) { const_cast(this)->load(); } return d->brushTipImage; } qint32 KisBrush::width() const { return d->width; } void KisBrush::setWidth(qint32 width) { d->width = width; } qint32 KisBrush::height() const { return d->height; } void KisBrush::setHeight(qint32 height) { d->height = height; } void KisBrush::setHotSpot(QPointF pt) { double x = pt.x(); double y = pt.y(); if (x < 0) x = 0; else if (x >= width()) x = width() - 1; if (y < 0) y = 0; else if (y >= height()) y = height() - 1; d->hotSpot = QPointF(x, y); } QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const { Q_UNUSED(info); QSizeF metric = characteristicSize(shape); qreal w = metric.width(); qreal h = metric.height(); // The smallest brush we can produce is a single pixel. if (w < 1) { w = 1; } if (h < 1) { h = 1; } // XXX: This should take d->hotSpot into account, though it // isn't specified by gimp brushes so it would default to the center // anyway. QPointF p(w / 2, h / 2); return p; } bool KisBrush::hasColor() const { return d->hasColor; } void KisBrush::setHasColor(bool hasColor) { d->hasColor = hasColor; } bool KisBrush::isPiercedApprox() const { QImage image = brushTipImage(); qreal w = image.width(); qreal h = image.height(); qreal xPortion = qMin(0.1, 5.0 / w); qreal yPortion = qMin(0.1, 5.0 / h); int x0 = std::floor((0.5 - xPortion) * w); int x1 = std::ceil((0.5 + xPortion) * w); int y0 = std::floor((0.5 - yPortion) * h); int y1 = std::ceil((0.5 + yPortion) * h); const int maxNumSamples = (x1 - x0 + 1) * (y1 - y0 + 1); const int failedPixelsThreshold = 0.1 * maxNumSamples; const int thresholdValue = 0.95 * 255; int failedPixels = 0; for (int y = y0; y <= y1; y++) { for (int x = x0; x <= x1; x++) { QRgb pixel = image.pixel(x,y); if (qRed(pixel) > thresholdValue) { failedPixels++; } } } return failedPixels > failedPixelsThreshold; } bool KisBrush::canPaintFor(const KisPaintInformation& /*info*/) { return true; } void KisBrush::setBrushTipImage(const QImage& image) { - //Q_ASSERT(!image.isNull()); d->brushTipImage = image; if (!image.isNull()) { if (image.width() > 128 || image.height() > 128) { KoResource::setImage(image.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { KoResource::setImage(image); } setWidth(image.width()); setHeight(image.height()); } clearBrushPyramid(); } void KisBrush::setBrushType(enumBrushType type) { d->brushType = type; } enumBrushType KisBrush::brushType() const { return d->brushType; } void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const { e.setAttribute("type", type); e.setAttribute("filename", shortFilename()); e.setAttribute("spacing", QString::number(spacing())); e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); e.setAttribute("angle", QString::number(angle())); e.setAttribute("scale", QString::number(scale())); } void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const { element.setAttribute("BrushVersion", "2"); } KisBrushSP KisBrush::fromXML(const QDomElement& element, bool forceCopy) { KisBrushSP brush = KisBrushRegistry::instance()->getOrCreateBrush(element, forceCopy); if (brush && element.attribute("BrushVersion", "1") == "1") { brush->setScale(brush->scale() * 2.0); } return brush; } QSizeF KisBrush::characteristicSize(KisDabShape const& shape) const { KisDabShape normalizedShape( shape.scale() * d->scale, shape.ratio(), normalizeAngle(shape.rotation() + d->angle)); return KisQImagePyramid::characteristicSize( QSize(width(), height()), normalizedShape); } qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); qreal angle = normalizeAngle(shape.rotation() + d->angle); qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).width(); } qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); qreal angle = normalizeAngle(shape.rotation() + d->angle); qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).height(); } double KisBrush::maskAngle(double angle) const { return normalizeAngle(angle + d->angle); } quint32 KisBrush::brushIndex(const KisPaintInformation& info) const { Q_UNUSED(info); return 0; } void KisBrush::setSpacing(double s) { if (s < 0.02) s = 0.02; d->spacing = s; } double KisBrush::spacing() const { return d->spacing; } void KisBrush::setAutoSpacing(bool active, qreal coeff) { d->autoSpacingCoeff = coeff; d->autoSpacingActive = active; } bool KisBrush::autoSpacingActive() const { return d->autoSpacingActive; } qreal KisBrush::autoSpacingCoeff() const { return d->autoSpacingCoeff; } void KisBrush::notifyStrokeStarted() { } void KisBrush::notifyCachedDabPainted(const KisPaintInformation& info) { Q_UNUSED(info); } void KisBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo) { Q_UNUSED(info); Q_UNUSED(seqNo); } void KisBrush::setThreadingAllowed(bool value) { d->threadingAllowed = value; } bool KisBrush::threadingAllowed() const { return d->threadingAllowed; } void KisBrush::prepareBrushPyramid() const { if (!d->brushPyramid) { d->brushPyramid = toQShared(new KisQImagePyramid(brushTipImage())); } } void KisBrush::clearBrushPyramid() { d->brushPyramid.clear(); } void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const { PlainColoringInformation pci(color.data()); generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor); } void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const { PaintDeviceColoringInformation pdci(src, maskWidth(shape, subPixelX, subPixelY, info)); generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor); } void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info_, double subPixelX, double subPixelY, qreal softnessFactor) const { Q_ASSERT(valid()); Q_UNUSED(info_); Q_UNUSED(softnessFactor); prepareBrushPyramid(); QImage outputImage = d->brushPyramid->createImage(KisDabShape( shape.scale() * d->scale, shape.ratio(), -normalizeAngle(shape.rotation() + d->angle)), subPixelX, subPixelY); qint32 maskWidth = outputImage.width(); qint32 maskHeight = outputImage.height(); dst->setRect(QRect(0, 0, maskWidth, maskHeight)); dst->lazyGrowBufferWithoutInitialization(); quint8* color = 0; if (coloringInformation) { if (dynamic_cast(coloringInformation)) { color = const_cast(coloringInformation->color()); } } const KoColorSpace *cs = dst->colorSpace(); qint32 pixelSize = cs->pixelSize(); quint8 *dabPointer = dst->data(); quint8 *rowPointer = dabPointer; quint8 *alphaArray = new quint8[maskWidth]; bool hasColor = this->hasColor(); for (int y = 0; y < maskHeight; y++) { const quint8* maskPointer = outputImage.constScanLine(y); if (coloringInformation) { for (int x = 0; x < maskWidth; x++) { if (color) { memcpy(dabPointer, color, pixelSize); } else { memcpy(dabPointer, coloringInformation->color(), pixelSize); coloringInformation->nextColumn(); } dabPointer += pixelSize; } } if (hasColor) { const quint8 *src = maskPointer; quint8 *dst = alphaArray; for (int x = 0; x < maskWidth; x++) { const QRgb *c = reinterpret_cast(src); *dst = KoColorSpaceMaths::multiply(255 - qGray(*c), qAlpha(*c)); src += 4; dst++; } } else { const quint8 *src = maskPointer; quint8 *dst = alphaArray; for (int x = 0; x < maskWidth; x++) { const QRgb *c = reinterpret_cast(src); *dst = KoColorSpaceMaths::multiply(255 - *src, qAlpha(*c)); src += 4; dst++; } } cs->applyAlphaU8Mask(rowPointer, alphaArray, maskWidth); rowPointer += maskWidth * pixelSize; dabPointer = rowPointer; if (!color && coloringInformation) { coloringInformation->nextRow(); } } delete[] alphaArray; } KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { Q_ASSERT(valid()); Q_UNUSED(info); double angle = normalizeAngle(shape.rotation() + d->angle); double scale = shape.scale() * d->scale; prepareBrushPyramid(); QImage outputImage = d->brushPyramid->createImage( KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY); KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace); Q_CHECK_PTR(dab); dab->convertFromQImage(outputImage, ""); return dab; } void KisBrush::resetBoundary() { delete d->boundary; d->boundary = 0; } void KisBrush::generateBoundary() const { KisFixedPaintDeviceSP dev; KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle()); if (brushType() == IMAGE || brushType() == PIPE_IMAGE) { dev = paintDevice(KoColorSpaceRegistry::instance()->rgb8(), inverseTransform, KisPaintInformation()); } else { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); dev = new KisFixedPaintDevice(cs); mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation()); } d->boundary = new KisBoundary(dev); d->boundary->generateBoundary(); } const KisBoundary* KisBrush::boundary() const { if (!d->boundary) generateBoundary(); return d->boundary; } void KisBrush::setScale(qreal _scale) { d->scale = _scale; } qreal KisBrush::scale() const { return d->scale; } void KisBrush::setAngle(qreal _rotation) { d->angle = _rotation; } qreal KisBrush::angle() const { return d->angle; } QPainterPath KisBrush::outline() const { return boundary()->path(); } void KisBrush::lodLimitations(KisPaintopLodLimitations *l) const { if (spacing() > 0.5) { l->limitations << KoID("huge-spacing", i18nc("PaintOp instant preview limitation", "Spacing > 0.5, consider disabling Instant Preview")); } } diff --git a/libs/brush/kis_png_brush.cpp b/libs/brush/kis_png_brush.cpp index 6709913dd9..879b28fa57 100644 --- a/libs/brush/kis_png_brush.cpp +++ b/libs/brush/kis_png_brush.cpp @@ -1,135 +1,154 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_png_brush.h" #include #include #include #include #include #include KisPngBrush::KisPngBrush(const QString& filename) : KisScalingSizeBrush(filename) { setBrushType(INVALID); setSpacing(0.25); setHasColor(false); } +KisPngBrush::KisPngBrush(const KisPngBrush &rhs) + : KisScalingSizeBrush(rhs) +{ + setSpacing(rhs.spacing()); + setBrushTipImage(rhs.brushTipImage()); + if (brushTipImage().isGrayscale()) { + setBrushType(MASK); + setHasColor(false); + } + else { + setBrushType(IMAGE); + setHasColor(true); + } + setValid(rhs.valid()); +} + KisBrush* KisPngBrush::clone() const { return new KisPngBrush(*this); } bool KisPngBrush::load() { QFile f(filename()); if (f.size() == 0) return false; if (!f.exists()) return false; if (!f.open(QIODevice::ReadOnly)) { warnKrita << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&f); f.close(); return res; } bool KisPngBrush::loadFromDevice(QIODevice *dev) { + // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = dev->readAll(); QBuffer buf(&data); buf.open(QIODevice::ReadOnly); QImageReader reader(&buf, "PNG"); if (!reader.canRead()) { - setValid(false); - return false; + dbgKrita << "Could not read brush" << filename() << ". Error:" << reader.errorString(); + setValid(false); + return false; } if (reader.textKeys().contains("brush_spacing")) { setSpacing(KisDomUtils::toDouble(reader.text("brush_spacing"))); } if (reader.textKeys().contains("brush_name")) { setName(reader.text("brush_name")); } else { QFileInfo info(filename()); setName(info.baseName()); } QImage image = reader.read(); if (image.isNull()) { - dbgKrita << "Could not read brush" << filename() << ". Error:" << reader.errorString(); - setValid(false); - return false; + dbgKrita << "Could not create image for" << filename() << ". Error:" << reader.errorString(); + setValid(false); + return false; } setBrushTipImage(image); - setValid(!brushTipImage().isNull()); + setValid(true); if (brushTipImage().isGrayscale()) { setBrushType(MASK); setHasColor(false); } else { setBrushType(IMAGE); setHasColor(true); } setWidth(brushTipImage().width()); setHeight(brushTipImage().height()); - return !brushTipImage().isNull(); + + return valid(); } bool KisPngBrush::save() { QFile f(filename()); if (!f.open(QFile::WriteOnly)) return false; bool res = saveToDevice(&f); f.close(); return res; } bool KisPngBrush::saveToDevice(QIODevice *dev) const { if(brushTipImage().save(dev, "PNG")) { KoResource::saveToDevice(dev); return true; } return false; } QString KisPngBrush::defaultFileExtension() const { return QString(".png"); } void KisPngBrush::toXML(QDomDocument& d, QDomElement& e) const { predefinedBrushToXML("png_brush", e); KisBrush::toXML(d, e); } diff --git a/libs/brush/kis_png_brush.h b/libs/brush/kis_png_brush.h index be62f002e6..d8ceb9cc20 100644 --- a/libs/brush/kis_png_brush.h +++ b/libs/brush/kis_png_brush.h @@ -1,40 +1,41 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PNG_BRUSH_ #define KIS_PNG_BRUSH_ #include "kis_scaling_size_brush.h" class BRUSH_EXPORT KisPngBrush : public KisScalingSizeBrush { public: /// Construct brush to load filename later as brush KisPngBrush(const QString& filename); - + KisPngBrush(const KisPngBrush &rhs); KisBrush* clone() const override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice *dev) const override; QString defaultFileExtension() const override; void toXML(QDomDocument& d, QDomElement& e) const override; + }; #endif diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp index cfc89d0617..28b4c7b72d 100644 --- a/libs/flake/KoCanvasControllerWidget.cpp +++ b/libs/flake/KoCanvasControllerWidget.cpp @@ -1,622 +1,621 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2008-2009 Thomas Zander * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006, 2009 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007 C. Boemann * Copyright (C) 2006-2008 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 "KoCanvasControllerWidget.h" #include "KoCanvasControllerWidget_p.h" #include "KoCanvasControllerWidgetViewport_p.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoCanvasBase.h" #include "KoCanvasObserverBase.h" #include "KoCanvasSupervisor.h" #include "KoToolManager_p.h" #include #include #include #include #include #include #include #include #include #include void KoCanvasControllerWidget::Private::setDocumentOffset() { // The margins scroll the canvas widget inside the viewport, not // the document. The documentOffset is meant to be the value that // the canvas must add to the update rect in its paint event, to // compensate. QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value()); q->proxyObject->emitMoveDocumentOffset(pt); QWidget *canvasWidget = canvas->canvasWidget(); if (canvasWidget) { // If it isn't an OpenGL canvas if (qobject_cast(canvasWidget) == 0) { QPoint diff = q->documentOffset() - pt; if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) { canvasWidget->scroll(-diff.x(), diff.y()); } else { canvasWidget->scroll(diff.x(), diff.y()); } } } q->setDocumentOffset(pt); } void KoCanvasControllerWidget::Private::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. int docH = q->documentSize().height() + q->margin(); int docW = q->documentSize().width() + q->margin(); int drawH = viewportWidget->height(); int drawW = viewportWidget->width(); QScrollBar *hScroll = q->horizontalScrollBar(); QScrollBar *vScroll = q->verticalScrollBar(); int horizontalReserve = vastScrollingFactor * drawW; int verticalReserve = vastScrollingFactor * drawH; int xMin = -horizontalReserve; int yMin = -verticalReserve; int xMax = docW - drawW + horizontalReserve; int yMax = docH - drawH + verticalReserve; hScroll->setRange(xMin, xMax); vScroll->setRange(yMin, yMax); int fontheight = QFontMetrics(q->font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontheight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontheight); } void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event) { if (!canvas) return; if (!canvas->viewConverter()) return; QPoint pointerPos; QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { pointerPos = mouseEvent->pos(); } else { QTabletEvent *tabletEvent = dynamic_cast(event); if (tabletEvent) { pointerPos = tabletEvent->pos(); } } QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset(); QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } #include void KoCanvasControllerWidget::Private::activate() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } KoCanvasBase *canvas = q->canvas(); Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observer->setObservedCanvas(canvas); } } } void KoCanvasControllerWidget::Private::unsetCanvas() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { if (observer->observedCanvas() == q->canvas()) { observer->unsetObservedCanvas(); } } } } //////////// KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent) : QAbstractScrollArea(parent) , KoCanvasController(actionCollection) , d(new Private(this)) { // We need to set this as QDeclarativeView sets them a bit differnt from QAbstractScrollArea setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // And then our own Viewport d->viewportWidget = new Viewport(this); setViewport(d->viewportWidget); d->viewportWidget->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus); setFrameStyle(0); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setAutoFillBackground(false); /* Fixes: apps starting at zero zoom. Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the widget means we no loger get rounding errors in zooming and we no longer end up with zero-zoom. Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible */ setMinimumSize(QSize(50, 50)); setMouseTracking(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY())); connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX())); connect(proxyObject, SIGNAL(moveDocumentOffset(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&))); } KoCanvasControllerWidget::~KoCanvasControllerWidget() { delete d; } void KoCanvasControllerWidget::activate() { d->activate(); } void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy) { Q_UNUSED(dx); Q_UNUSED(dy); d->setDocumentOffset(); } QSize KoCanvasControllerWidget::viewportSize() const { return viewport()->size(); } void KoCanvasControllerWidget::setDrawShadow(bool drawShadow) { d->viewportWidget->setDrawShadow(drawShadow); } void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent) { proxyObject->emitSizeChanged(resizeEvent->size()); // XXX: When resizing, keep the area we're looking at now in the // center of the resized view. d->resetScrollBars(); d->setDocumentOffset(); } void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas) { - Q_ASSERT(canvas); // param is not null if (d->canvas) { d->unsetCanvas(); proxyObject->emitCanvasRemoved(this); d->canvas->setCanvasController(0); d->canvas->canvasWidget()->removeEventFilter(this); } d->canvas = canvas; if (d->canvas) { d->canvas->setCanvasController(this); changeCanvasWidget(d->canvas->canvasWidget()); proxyObject->emitCanvasSet(this); QTimer::singleShot(0, this, SLOT(activate())); setPreferredCenterFractionX(0); setPreferredCenterFractionY(0); } } KoCanvasBase* KoCanvasControllerWidget::canvas() const { if (d->canvas.isNull()) return 0; return d->canvas; } void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget) { if (d->viewportWidget->canvas()) { widget->setCursor(d->viewportWidget->canvas()->cursor()); d->viewportWidget->canvas()->removeEventFilter(this); } d->viewportWidget->setCanvas(widget); setFocusProxy(d->canvas->canvasWidget()); } int KoCanvasControllerWidget::visibleHeight() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int height1; if (canvasWidget == 0) height1 = viewport()->height(); else height1 = qMin(viewport()->height(), canvasWidget->height()); int height2 = height(); return qMin(height1, height2); } int KoCanvasControllerWidget::visibleWidth() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int width1; if (canvasWidget == 0) width1 = viewport()->width(); else width1 = qMin(viewport()->width(), canvasWidget->width()); int width2 = width(); return qMin(width1, width2); } int KoCanvasControllerWidget::canvasOffsetX() const { int offset = -horizontalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->x() + frameWidth(); } return offset; } int KoCanvasControllerWidget::canvasOffsetY() const { int offset = -verticalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->y() + frameWidth(); } return offset; } void KoCanvasControllerWidget::updateCanvasOffsetX() { proxyObject->emitCanvasOffsetXChanged(canvasOffsetX()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionX((horizontalScrollBar()->value() + viewport()->width() / 2.0) / documentSize().width()); } void KoCanvasControllerWidget::updateCanvasOffsetY() { proxyObject->emitCanvasOffsetYChanged(canvasOffsetY()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionY((verticalScrollBar()->value() + verticalScrollBar()->pageStep() / 2.0) / documentSize().height()); } void KoCanvasControllerWidget::ensureVisible(KoShape *shape) { Q_ASSERT(shape); ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect())); } void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth) { QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight()); QRect viewRect = rect.toRect(); viewRect.translate(d->canvas->documentOrigin()); if (!viewRect.isValid() || currentVisible.contains(viewRect)) return; // its visible. Nothing to do. // if we move, we move a little more so the amount of times we have to move is less. int jumpWidth = smooth ? 0 : currentVisible.width() / 5; int jumpHeight = smooth ? 0 : currentVisible.height() / 5; if (!smooth && viewRect.width() + jumpWidth > currentVisible.width()) jumpWidth = 0; if (!smooth && viewRect.height() + jumpHeight > currentVisible.height()) jumpHeight = 0; int horizontalMove = 0; if (currentVisible.width() <= viewRect.width()) // center view horizontalMove = viewRect.center().x() - currentVisible.center().x(); else if (currentVisible.x() > viewRect.x()) // move left horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth; else if (currentVisible.right() < viewRect.right()) // move right horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth); int verticalMove = 0; if (currentVisible.height() <= viewRect.height()) // center view verticalMove = viewRect.center().y() - currentVisible.center().y(); if (currentVisible.y() > viewRect.y()) // move up verticalMove = viewRect.y() - currentVisible.y() - jumpHeight; else if (currentVisible.bottom() < viewRect.bottom()) // move down verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight); pan(QPoint(horizontalMove, verticalMove)); } void KoCanvasControllerWidget::recenterPreferred() { const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; QPointF center = preferredCenter(); // convert into a viewport based point center.rx() += d->canvas->canvasWidget()->x() + frameWidth(); center.ry() += d->canvas->canvasWidget()->y() + frameWidth(); // scroll to a new center point QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height()); setScrollBarValue(topLeft.toPoint()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomIn(const QPoint ¢er) { zoomBy(center, sqrt(2.0)); } void KoCanvasControllerWidget::zoomOut(const QPoint ¢er) { zoomBy(center, sqrt(0.5)); } void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom) { setPreferredCenterFractionX(1.0 * center.x() / documentSize().width()); setPreferredCenterFractionY(1.0 * center.y() / documentSize().height()); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoom, preferredCenter()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomTo(const QRect &viewRect) { qreal scale; if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height()) scale = 1.0 * viewport()->height() / viewRect.height(); else scale = 1.0 * viewport()->width() / viewRect.width(); zoomBy(viewRect.center(), scale); } void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter) { // Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates. if (KoCanvasController::documentSize() == sz) return; if (!recalculateCenter) { // assume the distance from the top stays equal and recalculate the center. setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width()); setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height()); } const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; KoCanvasController::setDocumentSize(sz); d->viewportWidget->setDocumentSize(sz); d->resetScrollBars(); // Always emit the new offset. updateCanvasOffsetX(); updateCanvasOffsetY(); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::setZoomWithWheel(bool zoom) { d->zoomWithWheel = zoom; } void KoCanvasControllerWidget::setVastScrolling(qreal factor) { d->vastScrollingFactor = factor; } QPointF KoCanvasControllerWidget::currentCursorPosition() const { QWidget *canvasWidget = d->canvas->canvasWidget(); const KoViewConverter *converter = d->canvas->viewConverter(); return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos()); } void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint = scrollBarValue(); setScrollBarValue(sourcePoint + distance); } void KoCanvasControllerWidget::panUp() { pan(QPoint(0, verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panDown() { pan(QPoint(0, -verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panLeft() { pan(QPoint(horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::panRight() { pan(QPoint(-horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint) { setPreferredCenterFractionX(viewPoint.x() / documentSize().width()); setPreferredCenterFractionY(viewPoint.y() / documentSize().height()); recenterPreferred(); } QPointF KoCanvasControllerWidget::preferredCenter() const { QPointF center; center.setX(preferredCenterFractionX() * documentSize().width()); center.setY(preferredCenterFractionY() * documentSize().height()); return center; } void KoCanvasControllerWidget::paintEvent(QPaintEvent *event) { QPainter gc(viewport()); d->viewportWidget->handlePaintEvent(gc, event); } void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event) { d->viewportWidget->handleDragEnterEvent(event); } void KoCanvasControllerWidget::dropEvent(QDropEvent *event) { d->viewportWidget->handleDropEvent(event); } void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event) { d->viewportWidget->handleDragMoveEvent(event); } void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event) { d->viewportWidget->handleDragLeaveEvent(event); } void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event) { if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) { const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5); zoomRelativeToPoint(event->pos(), zoomCoeff); event->accept(); } else QAbstractScrollArea::wheelEvent(event); } void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff) { const QPoint offset = scrollBarValue(); const QPoint mousePos(widgetPoint + offset); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoomCoeff, mousePos); d->ignoreScrollSignals = oldIgnoreScrollSignals; } bool KoCanvasControllerWidget::focusNextPrevChild(bool) { // we always return false meaning the canvas takes keyboard focus, but never gives it away. return false; } void KoCanvasControllerWidget::setMargin(int margin) { KoCanvasController::setMargin(margin); Q_ASSERT(d->viewportWidget); d->viewportWidget->setMargin(margin); } QPoint KoCanvasControllerWidget::scrollBarValue() const { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); return QPoint(hBar->value(), vBar->value()); } void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value) { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); hBar->setValue(value.x()); vBar->setValue(value.y()); } KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoCanvasControllerWidget.cpp" diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 48bbebf378..e715523199 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2494 +1,2495 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 2010 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 "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoOdfWorkaround.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include "KoOdfGradientBackground.h" #include // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), shadow(0), border(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q) : q_ptr(q), size(rhs.size), shapeId(rhs.shapeId), name(rhs.name), localMatrix(rhs.localMatrix), connectors(rhs.connectors), parent(0), // to be initialized later shapeManagers(), // to be initialized later toolDelegates(), // FIXME: how to initialize them? userData(rhs.userData ? rhs.userData->clone() : 0), stroke(rhs.stroke), fill(rhs.fill), inheritBackground(rhs.inheritBackground), inheritStroke(rhs.inheritStroke), dependees(), // FIXME: how to initialize them? shadow(0), // WARNING: not implemented in Krita border(0), // WARNING: not implemented in Krita clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0), clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0), additionalAttributes(rhs.additionalAttributes), additionalStyleAttributes(rhs.additionalStyleAttributes), filterEffectStack(0), // WARNING: not implemented in Krita transparency(rhs.transparency), hyperLink(rhs.hyperLink), zIndex(rhs.zIndex), runThrough(rhs.runThrough), visible(rhs.visible), printable(rhs.visible), geometryProtected(rhs.geometryProtected), keepAspect(rhs.keepAspect), selectable(rhs.selectable), detectCollision(rhs.detectCollision), protectContent(rhs.protectContent), textRunAroundSide(rhs.textRunAroundSide), textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft), textRunAroundDistanceTop(rhs.textRunAroundDistanceTop), textRunAroundDistanceRight(rhs.textRunAroundDistanceRight), textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom), textRunAroundThreshold(rhs.textRunAroundThreshold), textRunAroundContour(rhs.textRunAroundContour) { } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); if (parent) { parent->removeShape(q); } Q_FOREACH (KoShapeManager *manager, shapeManagers) { manager->shapeInterface()->notifyShapeDestructed(q); } shapeManagers.clear(); if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); Q_FOREACH (KoShape * shape, dependees) { shape->shapeChanged(type, q); } Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { listener->notifyShapeChangedImpl(type, q); } } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate *dd) : d_ptr(dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); d->listeners.clear(); delete d_ptr; } KoShape *KoShape::cloneShape() const { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!"); + qWarning() << shapeId() << "cannot be cloned"; return 0; } void KoShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(paintcontext); if (stroke()) { stroke()->paint(this, painter, converter); } } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb(QPointF(), size()); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QRectF KoShape::boundingRect(const QList &shapes) { QRectF boundingRect; Q_FOREACH (KoShape *shape, shapes) { boundingRect |= shape->boundingRect(); } return boundingRect; } QRectF KoShape::absoluteOutlineRect(KoViewConverter *converter) const { return absoluteTransformation(converter).map(outline()).boundingRect(); } QRectF KoShape::absoluteOutlineRect(const QList &shapes, KoViewConverter *converter) { QRectF absoluteOutlineRect; Q_FOREACH (KoShape *shape, shapes) { absoluteOutlineRect |= shape->absoluteOutlineRect(converter); } return absoluteOutlineRect; } QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { /** * WARNING: Our definition of zIndex is not yet compatible with SVG2's * definition. In SVG stacking context of groups with the same * zIndex are **merged**, while in Krita the contents of groups * is never merged. One group will always below than the other. * Therefore, when zIndex of two groups inside the same parent * coinside, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } bool KoShape::inheritsTransformFromAny(const QList ancestorsInQuestion) const { bool result = false; KoShape *shape = const_cast(this); while (shape) { KoShapeContainer *parent = shape->parent(); if (parent && !parent->inheritsTransform(shape)) { break; } if (ancestorsInQuestion.contains(shape)) { result = true; break; } shape = parent; } return result; } int KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::updateAbsolute(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { Q_D(const KoShape); if (background()) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; } void KoShape::notifyChanged() { Q_D(KoShape); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData.data(); } bool KoShape::hasTransparency() const { Q_D(const KoShape); QSharedPointer bg = background(); return !bg || bg->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); d->shapeChanged(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->inheritBackground = false; d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); QSharedPointer bg; if (!d->inheritBackground) { bg = d->fill; } else if (parent()) { bg = parent()->background(); } return bg; } void KoShape::setInheritBackground(bool value) { Q_D(KoShape); d->inheritBackground = value; if (d->inheritBackground) { d->fill.clear(); } } bool KoShape::inheritBackground() const { Q_D(const KoShape); return d->inheritBackground; } void KoShape::setZIndex(int zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (! recursive) return d->visible; if (recursive && ! d->visible) return false; KoShapeContainer * parentShape = parent(); while (parentShape) { if (! parentShape->isVisible()) return false; parentShape = parentShape->parent(); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; d->shapeChanged(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { Q_D(const KoShape); KoShapeStrokeModelSP stroke; if (!d->inheritStroke) { stroke = d->stroke; } else if (parent()) { stroke = parent()->stroke(); } return stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { Q_D(KoShape); d->inheritStroke = false; d->stroke = stroke; d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setInheritStroke(bool value) { Q_D(KoShape); d->inheritStroke = value; if (d->inheritStroke) { d->stroke.clear(); } } bool KoShape::inheritStroke() const { Q_D(const KoShape); return d->inheritStroke; } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath.reset(clipPath); d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { Q_D(KoShape); d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { Q_D(const KoShape); return d->clipMask.data(); } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isEditable() const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (d->parent && d->parent->isChildLocked(this)) return false; return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModelSP sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); d->stroke.clear(); if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); QSharedPointer stroke(new KoShapeStroke()); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { QSharedPointer stroke(new KoShapeStroke()); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return KoShapeStrokeModelSP(); } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse); d->clipPath.reset(clipPath); } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } context.xmlWriter().addAttributePt("svg:width", s.width()); context.xmlWriter().addAttributePt("svg:height", s.height()); } // The position is implicitly stored in the transformation matrix // if the transformation is saved as well if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) { const QPointF p(position() * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x", p.x()); context.xmlWriter().addAttributePt("svg:y", p.y()); } if (attributes & OdfTransformation) { QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this); if (! matrix.isIdentity()) { if (qAbs(matrix.m11() - 1) < 1E-5 // 1 && qAbs(matrix.m12()) < 1E-5 // 0 && qAbs(matrix.m21()) < 1E-5 // 0 && qAbs(matrix.m22() - 1) < 1E-5) { // 1 context.xmlWriter().addAttributePt("svg:x", matrix.dx()); context.xmlWriter().addAttributePt("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { context.xmlWriter().addAttributePt("svg:x", cp.value().position.x()); context.xmlWriter().addAttributePt("svg:y", cp.value().position.y()); } QString escapeDirection; switch(cp.value().escapeDirection) { case KoConnectionPoint::HorizontalDirections: escapeDirection = "horizontal"; break; case KoConnectionPoint::VerticalDirections: escapeDirection = "vertical"; break; case KoConnectionPoint::LeftDirection: escapeDirection = "left"; break; case KoConnectionPoint::RightDirection: escapeDirection = "right"; break; case KoConnectionPoint::UpDirection: escapeDirection = "up"; break; case KoConnectionPoint::DownDirection: escapeDirection = "down"; break; default: // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour wheras // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform()); KoShape::applyConversion(*painter, converter); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::linearizeSubtree(const QList &shapes) { QList result; Q_FOREACH (KoShape *shape, shapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtree(container->shapes()); } } return result; } diff --git a/libs/flake/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp index cc4c75fbdb..b9d6c31a2b 100644 --- a/libs/flake/KoShapeContainer.cpp +++ b/libs/flake/KoShapeContainer.cpp @@ -1,240 +1,243 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2007 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 "KoShapeContainer.h" #include "KoShapeContainer_p.h" #include "KoShapeContainerModel.h" #include "KoShapeStrokeModel.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoViewConverter.h" #include #include #include #include "kis_painting_tweaks.h" #include "kis_assert.h" KoShapeContainerPrivate::KoShapeContainerPrivate(KoShapeContainer *q) : KoShapePrivate(q), shapeInterface(q), model(0) { } KoShapeContainerPrivate::~KoShapeContainerPrivate() { delete model; } KoShapeContainerPrivate::KoShapeContainerPrivate(const KoShapeContainerPrivate &rhs, KoShapeContainer *q) : KoShapePrivate(rhs, q), shapeInterface(q), model(0) { } KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model) - : KoShape(new KoShapeContainerPrivate(this)) + : KoShape(new KoShapeContainerPrivate(this)) { Q_D(KoShapeContainer); d->model = model; } KoShapeContainer::KoShapeContainer(KoShapeContainerPrivate *dd) : KoShape(dd) { Q_D(KoShapeContainer); // HACK ALERT: the shapes are copied inside the model, // but we still need to connect the to the // hierarchy here! if (d->model) { Q_FOREACH (KoShape *shape, d->model->shapes()) { - shape->setParent(this); + if (shape) { // Note: shape can be 0 because not all shapes + // implement cloneShape, e.g. the text shape. + shape->setParent(this); + } } } } KoShapeContainer::~KoShapeContainer() { Q_D(KoShapeContainer); if (d->model) { d->model->deleteOwnedShapes(); } } void KoShapeContainer::addShape(KoShape *shape) { shape->setParent(this); } void KoShapeContainer::removeShape(KoShape *shape) { shape->setParent(0); } int KoShapeContainer::shapeCount() const { Q_D(const KoShapeContainer); if (d->model == 0) return 0; return d->model->count(); } bool KoShapeContainer::isChildLocked(const KoShape *child) const { Q_D(const KoShapeContainer); if (d->model == 0) return false; return d->model->isChildLocked(child); } void KoShapeContainer::setClipped(const KoShape *child, bool clipping) { Q_D(KoShapeContainer); if (d->model == 0) return; d->model->setClipped(child, clipping); } void KoShapeContainer::setInheritsTransform(const KoShape *shape, bool inherit) { Q_D(KoShapeContainer); if (d->model == 0) return; d->model->setInheritsTransform(shape, inherit); } bool KoShapeContainer::inheritsTransform(const KoShape *shape) const { Q_D(const KoShapeContainer); if (d->model == 0) return false; return d->model->inheritsTransform(shape); } void KoShapeContainer::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { // Shape container paints only its internal component part. All the children are rendered // by the shape manager itself Q_D(KoShapeContainer); painter.save(); paintComponent(painter, converter, paintcontext); painter.restore(); } void KoShapeContainer::shapeChanged(ChangeType type, KoShape* shape) { Q_UNUSED(shape); Q_D(KoShapeContainer); if (d->model == 0) return; if (!(type == RotationChanged || type == ScaleChanged || type == ShearChanged - || type == SizeChanged || type == PositionChanged || type == GenericMatrixChange)) + || type == SizeChanged || type == PositionChanged || type == GenericMatrixChange)) return; d->model->containerChanged(this, type); Q_FOREACH (KoShape *shape, d->model->shapes()) shape->notifyChanged(); } bool KoShapeContainer::isClipped(const KoShape *child) const { Q_D(const KoShapeContainer); if (d->model == 0) // throw exception?? return false; return d->model->isClipped(child); } void KoShapeContainer::update() const { Q_D(const KoShapeContainer); KoShape::update(); if (d->model) Q_FOREACH (KoShape *shape, d->model->shapes()) shape->update(); } QList KoShapeContainer::shapes() const { Q_D(const KoShapeContainer); if (d->model == 0) return QList(); return d->model->shapes(); } KoShapeContainerModel *KoShapeContainer::model() const { Q_D(const KoShapeContainer); return d->model; } KoShapeContainer::ShapeInterface *KoShapeContainer::shapeInterface() { Q_D(KoShapeContainer); return &d->shapeInterface; } KoShapeContainer::ShapeInterface::ShapeInterface(KoShapeContainer *_q) : q(_q) { } void KoShapeContainer::ShapeInterface::addShape(KoShape *shape) { KoShapeContainerPrivate * const d = q->d_func(); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); if (shape->parent() == q && q->shapes().contains(shape)) { return; } // TODO add a method to create a default model depending on the shape container if (!d->model) { d->model = new SimpleShapeContainerModel(); } if (shape->parent() && shape->parent() != q) { shape->parent()->shapeInterface()->removeShape(shape); } d->model->add(shape); d->model->shapeHasBeenAddedToHierarchy(shape, q); } void KoShapeContainer::ShapeInterface::removeShape(KoShape *shape) { KoShapeContainerPrivate * const d = q->d_func(); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->model); KIS_SAFE_ASSERT_RECOVER_RETURN(d->model->shapes().contains(shape)); d->model->shapeToBeRemovedFromHierarchy(shape, q); d->model->remove(shape); KoShapeContainer *grandparent = q->parent(); if (grandparent) { grandparent->model()->childChanged(q, KoShape::ChildChanged); } } diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index 535ebfa68d..b462828e48 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,513 +1,512 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2006-2011 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 "KoToolProxy.h" #include "KoToolProxy_p.h" #include #include #include #include #include #include #include #include #include #include #include "KoToolBase.h" #include "KoPointerEvent.h" #include "KoInputDevice.h" #include "KoToolManager_p.h" #include "KoToolSelection.h" #include "KoCanvasBase.h" #include "KoCanvasController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoOdf.h" #include "KoViewConverter.h" #include "KoShapeFactoryBase.h" KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : activeTool(0), tabletPressed(false), hasSelection(false), controller(0), parent(p) { scrollTimer.setInterval(100); mouseLeaveWorkaround = false; multiClickCount = 0; } void KoToolProxyPrivate::timeout() // Auto scroll the canvas { Q_ASSERT(controller); QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint origin = controller->canvas()->documentOrigin(); QPoint viewPoint = widgetScrollPoint - origin - offset; QRectF mouseArea(viewPoint, QSizeF(10, 10)); mouseArea.setTopLeft(mouseArea.center()); controller->ensureVisible(mouseArea, true); QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint moved = offset - newOffset; if (moved.isNull()) return; widgetScrollPoint += moved; QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint); QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0); KoPointerEvent ev(&event, documentPoint); activeTool->mouseMoveEvent(&ev); } void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event) { if (controller == 0) return; if (!activeTool) return; if (!activeTool->wantsAutoScroll()) return; if (!event.isAccepted()) return; if (event.buttons() != Qt::LeftButton) return; widgetScrollPoint = event.pos(); if (! scrollTimer.isActive()) scrollTimer.start(); } void KoToolProxyPrivate::selectionChanged(bool newSelection) { if (hasSelection == newSelection) return; hasSelection = newSelection; emit parent->selectionChanged(hasSelection); } bool KoToolProxyPrivate::isActiveLayerEditable() { if (!activeTool) return false; KoShapeManager * shapeManager = activeTool->canvas()->shapeManager(); KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer(); if (activeLayer && !activeLayer->isEditable()) return false; return true; } KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent) : QObject(parent), d(new KoToolProxyPrivate(this)) { KoToolManager::instance()->priv()->registerToolProxy(this, canvas); connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout())); } KoToolProxy::~KoToolProxy() { delete d; } void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter) { if (d->activeTool) d->activeTool->paint(painter, converter); } void KoToolProxy::repaintDecorations() { if (d->activeTool) d->activeTool->repaintDecorations(); } QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const { QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY()); QPoint origin = d->controller->canvas()->documentOrigin(); QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset); return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint); } KoCanvasBase* KoToolProxy::canvas() const { return d->controller->canvas(); } void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point) { // We get these events exclusively from KisToolProxy - accept them event->accept(); KoInputDevice id(event->device(), event->pointerType(), event->uniqueId()); KoToolManager::instance()->priv()->switchInputDevice(id); KoPointerEvent ev(event, point); switch (event->type()) { case QEvent::TabletPress: ev.setTabletButton(Qt::LeftButton); if (!d->tabletPressed && d->activeTool) d->activeTool->mousePressEvent(&ev); d->tabletPressed = true; break; case QEvent::TabletRelease: ev.setTabletButton(Qt::LeftButton); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) d->activeTool->mouseReleaseEvent(&ev); break; case QEvent::TabletMove: if (d->tabletPressed) ev.setTabletButton(Qt::LeftButton); if (d->activeTool) d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); default: ; // ignore the rest. } d->mouseLeaveWorkaround = true; } void KoToolProxy::mousePressEvent(KoPointerEvent *ev) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->mouseDownPoint = ev->pos(); if (d->tabletPressed) // refuse to send a press unless there was a release first. return; QPointF globalPoint = ev->globalPos(); if (d->multiClickGlobalPoint != globalPoint) { if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5|| qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) { d->multiClickCount = 0; } d->multiClickGlobalPoint = globalPoint; } if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) { // One more multiclick; d->multiClickCount++; } else { d->multiClickTimeStamp.start(); d->multiClickCount = 1; } if (d->activeTool) { switch (d->multiClickCount) { case 0: case 1: d->activeTool->mousePressEvent(ev); break; case 2: d->activeTool->mouseDoubleClickEvent(ev); break; case 3: default: d->activeTool->mouseTripleClickEvent(ev); break; } } else { d->multiClickCount = 0; ev->ignore(); } } void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mousePressEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseDoubleClickEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event) { // let us handle it as any other mousepress (where we then detect multi clicks mousePressEvent(event); } void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseMoveEvent(&ev); } void KoToolProxy::mouseMoveEvent(KoPointerEvent *event) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } d->activeTool->mouseMoveEvent(event); d->checkAutoScroll(*event); } void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseReleaseEvent(&ev); } void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(event); } else { event->ignore(); } } void KoToolProxy::keyPressEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyPressEvent(event); else event->ignore(); } void KoToolProxy::keyReleaseEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyReleaseEvent(event); else event->ignore(); } void KoToolProxy::explicitUserStrokeEndRequest() { if (d->activeTool) { d->activeTool->explicitUserStrokeEndRequest(); } } QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (d->activeTool) return d->activeTool->inputMethodQuery(query, converter); return QVariant(); } void KoToolProxy::inputMethodEvent(QInputMethodEvent *event) { if (d->activeTool) d->activeTool->inputMethodEvent(event); } QMenu *KoToolProxy::popupActionsMenu() { return d->activeTool ? d->activeTool->popupActionsMenu() : 0; } void KoToolProxy::setActiveTool(KoToolBase *tool) { if (d->activeTool) disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->activeTool = tool; if (tool) { connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->selectionChanged(hasSelection()); emit toolChanged(tool->toolId()); } } void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } QHash KoToolProxy::actions() const { return d->activeTool ? d->activeTool->actions() : QHash(); } bool KoToolProxy::hasSelection() const { return d->activeTool ? d->activeTool->hasSelection() : false; } void KoToolProxy::cut() { if (d->activeTool && d->isActiveLayerEditable()) d->activeTool->cut(); } void KoToolProxy::copy() const { if (d->activeTool) d->activeTool->copy(); } bool KoToolProxy::paste() { bool success = false; KoCanvasBase *canvas = d->controller->canvas(); if (d->activeTool && d->isActiveLayerEditable()) { success = d->activeTool->paste(); } if (!success) { const QMimeData *data = QApplication::clipboard()->mimeData(); QList imageList; QImage image = QApplication::clipboard()->image(); if (!image.isNull()) { imageList << image; } - // QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks. else if (data->hasUrls()) { QList urls = QApplication::clipboard()->mimeData()->urls(); foreach (const QUrl &url, urls) { QImage image; image.load(url.toLocalFile()); if (!image.isNull()) { imageList << image; } } } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape"); QWidget *canvasWidget = canvas->canvasWidget(); const KoViewConverter *converter = canvas->viewConverter(); if (imageList.length() > 0 && factory && canvasWidget) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image")); QList pastedShapes; Q_FOREACH (const QImage &image, imageList) { if (!image.isNull()) { QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos()); KoProperties params; params.setProperty("qimage", image); KoShape *shape = factory->createShape(¶ms, canvas->shapeController()->resourceManager()); shape->setPosition(p); pastedShapes << shape; success = true; } } if (!pastedShapes.isEmpty()) { // add shape to the document canvas->shapeController()->addShapesDirect(pastedShapes, 0, cmd); canvas->addCommand(cmd); } } } return success; } void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dragMoveEvent(event, point); } void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event) { if (d->activeTool) d->activeTool->dragLeaveEvent(event); } void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dropEvent(event, point); } void KoToolProxy::deleteSelection() { if (d->activeTool) d->activeTool->deleteSelection(); } void KoToolProxy::processEvent(QEvent *e) const { if(e->type()==QEvent::ShortcutOverride && d->activeTool && d->activeTool->isInTextMode() && (static_cast(e)->modifiers()==Qt::NoModifier || static_cast(e)->modifiers()==Qt::ShiftModifier)) { e->accept(); } } void KoToolProxy::requestUndoDuringStroke() { if (d->activeTool) { d->activeTool->requestUndoDuringStroke(); } } void KoToolProxy::requestStrokeCancellation() { if (d->activeTool) { d->activeTool->requestStrokeCancellation(); } } void KoToolProxy::requestStrokeEnd() { if (d->activeTool) { d->activeTool->requestStrokeEnd(); } } KoToolProxyPrivate *KoToolProxy::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolProxy.cpp" diff --git a/libs/flake/SimpleShapeContainerModel.h b/libs/flake/SimpleShapeContainerModel.h index 8da4968602..8f870fa181 100644 --- a/libs/flake/SimpleShapeContainerModel.h +++ b/libs/flake/SimpleShapeContainerModel.h @@ -1,130 +1,134 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SIMPLESHAPECONTAINERMODEL_H #define SIMPLESHAPECONTAINERMODEL_H #include "KoShapeContainerModel.h" #include /// \internal class SimpleShapeContainerModel: public KoShapeContainerModel { public: SimpleShapeContainerModel() {} ~SimpleShapeContainerModel() override {} SimpleShapeContainerModel(const SimpleShapeContainerModel &rhs) : KoShapeContainerModel(rhs), m_inheritsTransform(rhs.m_inheritsTransform), m_clipped(rhs.m_clipped) { Q_FOREACH (KoShape *shape, rhs.m_members) { - m_members << shape->cloneShape(); + KoShape *clone = shape->cloneShape(); + KIS_SAFE_ASSERT_RECOVER_NOOP(clone && "Copying this shape is not implemented!"); + if (clone) { + m_members << clone; + } } KIS_ASSERT_RECOVER(m_members.size() == m_inheritsTransform.size() && m_members.size() == m_clipped.size()) { qDeleteAll(m_members); m_members.clear(); m_inheritsTransform.clear(); m_clipped.clear(); } } void add(KoShape *child) override { if (m_members.contains(child)) return; m_members.append(child); m_clipped.append(false); m_inheritsTransform.append(true); } void setClipped(const KoShape *shape, bool value) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_clipped[index] = value; } bool isClipped(const KoShape *shape) const override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER(index >= 0) { return false;} return m_clipped[index]; } void remove(KoShape *shape) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_members.removeAt(index); m_clipped.removeAt(index); m_inheritsTransform.removeAt(index); } int count() const override { return m_members.count(); } QList shapes() const override { return QList(m_members); } void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { } bool isChildLocked(const KoShape *child) const override { Q_ASSERT(child->parent()); if (child->parent()) { return child->isGeometryProtected() || child->parent()->isGeometryProtected(); } else { return child->isGeometryProtected(); } } void setInheritsTransform(const KoShape *shape, bool value) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_inheritsTransform[index] = value; } bool inheritsTransform(const KoShape *shape) const override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER(index >= 0) { return true;} return m_inheritsTransform[index]; } void proposeMove(KoShape *shape, QPointF &move) override { KoShapeContainer *parent = shape->parent(); bool allowedToMove = true; while (allowedToMove && parent) { allowedToMove = parent->isEditable(); parent = parent->parent(); } if (! allowedToMove) { move.setX(0); move.setY(0); } } private: int indexOf(const KoShape *shape) const { // workaround indexOf constness! return m_members.indexOf(const_cast(shape)); } private: // members QList m_members; QList m_inheritsTransform; QList m_clipped; }; #endif diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index 0715644945..7e5ac8b232 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1811 +1,1812 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include #include #include "kis_dom_utils.h" #include "kis_debug.h" #include "kis_global.h" #include struct SvgParser::DeferredUseStore { struct El { El(const KoXmlElement* ue, const QString& key) : m_useElement(ue), m_key(key) { } const KoXmlElement* m_useElement; QString m_key; }; DeferredUseStore(SvgParser* p) : m_parse(p) { } void add(const KoXmlElement* useE, const QString& key) { m_uses.push_back(El(useE, key)); } bool empty() const { return m_uses.empty(); } void checkPendingUse(const KoXmlElement &b, QList& shapes) { KoShape* shape = 0; const QString id = b.attribute("id"); if (id.isEmpty()) return; // qDebug() << "Checking id: " << id; auto i = std::partition(m_uses.begin(), m_uses.end(), [&](const El& e) -> bool {return e.m_key != id;}); while (i != m_uses.end()) { const El& el = m_uses.back(); if (m_parse->m_context.hasDefinition(el.m_key)) { // qDebug() << "Found pending use for id: " << el.m_key; shape = m_parse->resolveUse(*(el.m_useElement), el.m_key); if (shape) { shapes.append(shape); } } m_uses.pop_back(); } } ~DeferredUseStore() { while (!m_uses.empty()) { const El& el = m_uses.back(); qDebug() << "WARNING: could not find path in m_uses; }; SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { qDeleteAll(m_symbols); } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); setFileFetcher( [this](const QString &name) { const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name; QFile file(fileName); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), QByteArray()); file.open(QIODevice::ReadOnly); return file.readAll(); }); } void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch) { KIS_ASSERT(!m_context.currentGC()); m_context.pushGraphicsContext(); m_context.currentGC()->isResolutionFrame = true; m_context.currentGC()->pixelsPerInch = pixelsPerInch; const qreal scale = 72.0 / pixelsPerInch; const QTransform t = QTransform::fromScale(scale, scale); m_context.currentGC()->currentBoundingBox = boundsInPixels; m_context.currentGC()->matrix = t; } QList SvgParser::shapes() const { return m_shapes; } QVector SvgParser::takeSymbols() { QVector symbols = m_symbols; m_symbols.clear(); return symbols; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (KoXml::childNodesCount(e) == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape) { /** * Unlike the gradient parsing function, this method is called every time we * *reference* the pattern, not when we define it. Therefore we can already * use the coordinate system of the destination. */ QSharedPointer pattHelper; SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return pattHelper; const QString patternId = e.attribute("id"); if (patternId.isEmpty()) return pattHelper; pattHelper = toQShared(new KoVectorPatternBackground); if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty() &&href != patternId) { // copy the referenced pattern if found QSharedPointer pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * * TODO: Please also note that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer marker(new KoMarker()); marker->setCoordinateSystem( KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth"))); marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")), parseUnitY(e.attribute("refY")))); marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")), parseUnitY(e.attribute("markerHeight", "3")))); const QString orientation = e.attribute("orient", "0"); if (orientation == "auto") { marker->setAutoOrientation(true); } else { marker->setExplicitOrientation(parseAngular(orientation)); } // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize()); KoShape *markerShape = parseGroup(e); m_context.popGraphicsContext(); if (!markerShape) return false; marker->setShapes({markerShape}); m_markers.insert(id, QExplicitlySharedDataPointer(marker.take())); return true; } bool SvgParser::parseSymbol(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; KoSvgSymbol *svgSymbol = new KoSvgSymbol(); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0); QString title = e.firstChildElement("title").toElement().text(); KoShape *symbolShape = parseGroup(e); m_context.popGraphicsContext(); if (!symbolShape) return false; svgSymbol->shape = symbolShape; svgSymbol->title = title; svgSymbol->id = id; if (title.isEmpty()) svgSymbol->title = id; if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) { warnFlake << "Symbol" << id << "seems to be empty, discarding"; delete svgSymbol; return false; } m_symbols << svgSymbol; return true; } bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; applyCurrentBasicStyle(shape); if (KoPathShape *pathShape = dynamic_cast(shape)) { applyMarkers(pathShape); } applyFilter(shape); applyClipping(shape, shapeToOriginalUserCoordinates); applyMaskClipping(shape, shapeToOriginalUserCoordinates); } void SvgParser::applyCurrentBasicStyle(KoShape *shape) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (!gc->display || !gc->visible) { /** * WARNING: here is a small inconsistency with the standard: * in the standard, 'display' is not inherited, but in * flake it is! * * NOTE: though the standard says: "A value of 'display:none' indicates * that the given element and ***its children*** shall not be * rendered directly". Therefore, using setVisible(false) is fully * legitimate here (DK 29.11.16). */ shape->setVisible(false); } shape->setTransparency(1.0 - gc->opacity); } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates) { applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(gradient->gradient()); QLinearGradient *g = new QLinearGradient(); g->setStart(userToRelative.map(o->start())); g->setFinalStop(userToRelative.map(o->finalStop())); g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStops(o->stops()); g->setSpread(o->spread()); resultGradient = g; *transform = relativeToUser * gradient->transform() * userToRelative; } else if (gradient->gradient()->type() == QGradient::RadialGradient) { // For radial and conical gradients such conversion is not possible resultGradient = KoFlake::cloneGradient(gradient->gradient()); + resultGradient->setCoordinateMode(QGradient::ObjectBoundingMode); *transform = gradient->transform() * gc->matrix * shape->transformation().inverted(); } } return resultGradient; } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector dashes = srcStroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) { dashes[i] /= lineWidth; } dstStroke->setLineStyle(Qt::CustomDashLine, dashes); dstStroke->setDashOffset(dashOffset / lineWidth); } else { dstStroke->setLineStyle(Qt::SolidLine, QVector()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox transformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e, DeferredUseStore* deferredUseStore) { QString href = e.attribute("xlink:href"); if (href.isEmpty()) return 0; QString key = href.mid(1); const bool gotDef = m_context.hasDefinition(key); if (gotDef) { return resolveUse(e, key); } if (!gotDef && deferredUseStore) { deferredUseStore->add(&e, key); return 0; } qDebug() << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: " << key; return 0; } KoShape* SvgParser::resolveUse(const KoXmlElement &e, const QString& key) { KoShape *result = 0; SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: parse 'width' and 'height' as well gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); const KoXmlElement &referencedElement = m_context.definition(key); result = parseGroup(e, referencedElement); m_context.popGraphicsContext(); return result; } void SvgParser::addToGroup(QList shapes, KoShapeContainer *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not clipped, but inherit transform KoShapeGroupCommand cmd(group, shapes, false, true, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w); const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h); QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; // First find the metadata for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (b.tagName() == "title") { m_documentTitle = b.text().trimmed(); } else if (b.tagName() == "desc") { m_documentDescription = b.text().trimmed(); } else if (b.tagName() == "metadata") { // TODO: parse the metadata } } // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy // and as mother makes them -- if mother is inkscape. if (gc->currentBoundingBox.normalized().isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } QString SvgParser::documentTitle() const { return m_documentTitle; } QString SvgParser::documentDescription() const { return m_documentDescription; } void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * coordinateSystemOnLoading; KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate); return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy()); } KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom) { m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); // groups should also have their own coordinate system! group->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix); uploadStyleToContext(b); QList childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom, 0); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } KoShape* SvgParser::parseTextNode(const KoXmlText &e) { QScopedPointer textChunk(new KoSvgTextChunkShape()); textChunk->setZIndex(m_context.nextZIndex()); if (!textChunk->loadSvgTextNode(e, m_context)) { return 0; } applyCurrentBasicStyle(textChunk.data()); // apply style to this group after size is set return textChunk.take(); } KoXmlText getTheOnlyTextChild(const KoXmlElement &e) { KoXmlNode firstChild = e.firstChild(); return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ? firstChild.toText() : KoXmlText(); } KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *mergeIntoShape) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan", 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_isInsideTextSubtree || e.tagName() == "text", 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0); KoSvgTextShape *rootTextShape = 0; if (e.tagName() == "text") { // XXX: Shapes need to be created by their factories rootTextShape = mergeIntoShape ? mergeIntoShape : new KoSvgTextShape(); } if (rootTextShape) { m_isInsideTextSubtree = true; } m_context.pushGraphicsContext(e); uploadStyleToContext(e); KoSvgTextChunkShape *textChunk = rootTextShape ? rootTextShape : new KoSvgTextChunkShape(); textChunk->setZIndex(m_context.nextZIndex()); textChunk->loadSvg(e, m_context); // 1) apply transformation only in case we are not overriding the shape! // 2) the transformation should be applied *before* the shape is added to the group! if (!mergeIntoShape) { // groups should also have their own coordinate system! textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(textChunk, m_context.currentGC()->matrix); // handle id applyId(e.attribute("id"), textChunk); applyCurrentStyle(textChunk, extraOffset); // apply style to this group after size is set } else { applyCurrentBasicStyle(textChunk); } KoXmlText onlyTextChild = getTheOnlyTextChild(e); if (!onlyTextChild.isNull()) { textChunk->loadSvgTextNode(onlyTextChild, m_context); } else { QList childShapes = parseContainer(e, true); addToGroup(childShapes, textChunk); } m_context.popGraphicsContext(); textChunk->normalizeCharTransformations(); if (rootTextShape) { textChunk->simplifyFillStrokeInheritance(); m_isInsideTextSubtree = false; rootTextShape->relayout(); } return textChunk; } QList SvgParser::parseContainer(const KoXmlElement &e, bool parseTextNodes) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; DeferredUseStore deferredUseStore(this); for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) { if (parseTextNodes && n.isText()) { KoShape *shape = parseTextNode(n.toText()); if (shape) { shapes += shape; } } continue; } if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemeted yet } } QList currentShapes = parseSingleElement(b, &deferredUseStore); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } void SvgParser::parseDefsElement(const KoXmlElement &e) { KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs"); parseSingleElement(e); } QList SvgParser::parseSingleElement(const KoXmlElement &b, DeferredUseStore* deferredUseStore) { QList shapes; // save definition for later instantiation with 'use' m_context.addDefinition(b); if (deferredUseStore) { deferredUseStore->checkPendingUse(b, shapes); } if (b.tagName() == "svg") { shapes += parseSvg(b); } else if (b.tagName() == "g" || b.tagName() == "a") { // treat svg link as group so we don't miss its child elements shapes += parseGroup(b); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { if (KoXml::childNodesCount(b) > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); m_defsShapes << defsShape; // TODO: where to delete the shape!? } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); } else if (b.tagName() == "symbol") { parseSymbol(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "text" || b.tagName() == "tspan") { shapes += parseTextElement(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image") { KoShape *shape = createObjectDirect(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { KoShape* s = parseUse(b, deferredUseStore); if (s) { shapes += s; } } else if (b.tagName() == "color-profile") { m_context.parseProfile(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } } return shapes; } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QStringList pointList = SvgUtil::simplifyList(element.attribute("points")); for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it))); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it))); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } KoShape *SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) { shape->setShapeId(factory->id()); } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border // ??? KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index cf0c812bb7..0cd23422e5 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1729 +1,1724 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , recorder(_q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg; if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; KisActionRecorder recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); setObjectName(rhs.objectName()); m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; if (rhs.m_d->proofingConfig) { m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); if (exactCopy) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); node->setUuid(refNode->uuid()); }); } Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { stopIsolatedMode(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { stopIsolatedMode(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, shearOrigin); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, shearOrigin); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint((int)pixelCoord.x(), (int)pixelCoord.y()); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } -QRect KisImage::documentToIntPixel(const QRectF &documentRect) const -{ - return documentToPixel(documentRect).toAlignedRect(); -} - QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return &m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { stopIsolatedMode(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { if (!tryBarrierLock()) return false; unlock(); m_d->isolatedRootNode = node; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; KisNodeSP oldRootNode = m_d->isolatedRootNode; m_d->isolatedRootNode = 0; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // udpate filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } - return 0; + return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index 2f1afab7a8..07206b8291 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,995 +1,988 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_paint_device.h" // msvc cannot handle forward declarations, so include kis_paint_device here #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KisDocument; class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisActionRecorder; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @param colorSpace can be null. in that case it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); ~KisImage() override; public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * XXX: docs! */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * Calls KisUpdateScheduler::lock (XXX: APIDOX -- what does that mean?) */ void lock(); /** * Calls KisUpdateScheduler::unlock (XXX: APIDOX -- what does that mean?) */ void unlock(); /** * Returns true if lock() has been called more often than unlock(). */ bool locked() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * Resize the image to the specified rect. The resize * method handles the creating on an undo step itself. * * @param newRect the rect describing the new width, height and offset * of the image */ void resizeImage(const QRect& newRect); /** * Crop the image to the specified rect. The crop * method handles the creating on an undo step itself. * * @param newRect the rect describing the new width, height and offset * of the image */ void cropImage(const QRect& newRect); /** * Crop a node to @newRect. The node will *not* be moved anywhere, * it just drops some content */ void cropNode(KisNodeSP node, const QRect& newRect); /// XXX: ApiDox void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /// XXX: ApiDox void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy); /** * Execute a rotate transform on all layers in this image. * Image is resized to fit rotated image. */ void rotateImage(double radians); /** * Execute a rotate transform on on a subtree of this image. * Image is not resized. */ void rotateNode(KisNodeSP node, double radians); /** * Execute a shear transform on all layers in this image. */ void shear(double angleX, double angleY); /** * Shear a node and all its children. * @param angleX, @param angleY are given in degrees. */ void shearNode(KisNodeSP node, double angleX, double angleY); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * NOTE: Note conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * adn executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * @return the action recorder associated with this image */ KisActionRecorder* actionRecorder() const; /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToIntPixel(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; - /** - * Convert a document rectangle to an integer pixel rectangle. - * - * @param documentRect PostScript Pt rectangle to convert. - */ - QRect documentToIntPixel(const QRectF &documentRect) const; - /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is delted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return curent level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. * * @param rc The rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief barrierLock APIDOX * @param readOnly */ void barrierLock(bool readOnly = false); /** * @brief barrierLock APIDOX * @param readOnly */ bool tryBarrierLock(bool readOnly = false); /** * @brief barrierLock APIDOX * @param readOnly */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. */ void disableUIUpdates() override; /** * \see disableUIUpdates */ void enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to dintinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 9cadbc02ae..9e469ab726 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,2906 +1,2906 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" #include "krita_utils.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #define trunc(x) ((int)(x)) #ifndef Q_OS_WIN #endif #include "kis_painter_p.h" KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstRect.topLeft(), src, srcRect); } else { gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); do { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } while (srcIt.nextPixel() && dstIt.nextPixel()); return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); do { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } while (srcIt.nextPixel() && dstIt.nextPixel()); return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); do { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } while(it.nextPixel()); return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (std::bad_alloc) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { - if (index >= (int) points.count()) + if (index >= points.count()) return; if (numPoints < 0) numPoints = points.count(); - if (index + numPoints > (int) points.count()) + if (index + numPoints > points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: // Fall through case FillStyleGradient: // Currently unsupported, fall through case FillStyleStrokes: // Currently unsupported, fall through warnImage << "Unknown or unsupported fill style in fillPolygon\n"; case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() Q_ASSERT(pen.color() == Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = int(start.x()); int y = int(start.y()); int x2 = int(end.x()); int y2 = int(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor mycolor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = int(fx + 1) - fx; float br2 = fx - (int)fx; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = int(fy + 1) - fy; float br2 = fy - (int)fy; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor lineColor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; - ix1 = (int)x1; - ix2 = (int)x2; - iy1 = (int)y1; + ix1 = x1; + ix2 = x2; + iy1 = y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; - iy1 = (int)y1; - iy2 = (int)y2; - ix1 = (int)x1; + iy1 = y1; + iy2 = y2; + ix1 = x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y interger coordinates xend = static_cast(x1 + 0.5f); yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = trunc(x2 + 0.5f); yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, int (yf)); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, int (yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y interger coordinates yend = static_cast(y1 + 0.5f); xend = x1 + grad * (yend - y1); ygap = invertFrac(y1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = trunc(y2 + 0.5f); xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(int (xf), y); if (selectionAccessor) selectionAccessor->moveTo(int (xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(int (xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int (xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - int (yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - int (yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, (int)yfa); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfa); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, (int)yfb); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfb); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, int (yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, int (yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = yfa + 1; i <= yfb; i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = yfa + 1; i >= yfb; i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - int (xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - int (xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(int (xfa), y); if (selectionAccessor) selectionAccessor->moveTo(int (xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(int(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = (int) xfa + 1; i <= (int) xfb; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = (int) xfb; i <= (int) xfa + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPattern * pattern) { d->pattern = pattern; } const KoPattern * KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setAverageOpacity(qreal averageOpacity) { d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity); } qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity) { const float exponent = 0.1; return averageOpacity < opacity ? opacity : exponent * opacity + (1.0 - exponent) * (averageOpacity); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } bool KisPainter::hasHorizontalMirroring() const { return d->mirrorHorizontally; } bool KisPainter::hasVerticalMirroring() const { return d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface) { d->runnableStrokeJobsInterface = interface; } KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const { if (!d->runnableStrokeJobsInterface) { if (!d->fakeRunnableStrokeJobsInterface) { d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor()); } return d->fakeRunnableStrokeJobsInterface.data(); } return d->runnableStrokeJobsInterface; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc); } void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab); } diff --git a/libs/image/metadata/kis_meta_data_value.cc b/libs/image/metadata/kis_meta_data_value.cc index 31e4dd6674..5273f886de 100644 --- a/libs/image/metadata/kis_meta_data_value.cc +++ b/libs/image/metadata/kis_meta_data_value.cc @@ -1,468 +1,454 @@ /* * Copyright (c) 2007,2010 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_value.h" -#include #include #include #include +#include #include #include #include #include using namespace KisMetaData; struct Q_DECL_HIDDEN Value::Private { Private() : type(Invalid) {} union { QVariant* variant; QList* array; QMap* structure; KisMetaData::Rational* rational; } value; ValueType type; QMap propertyQualifiers; }; Value::Value() : d(new Private) { d->type = Invalid; } Value::Value(const QVariant& variant) : d(new Private) { d->type = Value::Variant; d->value.variant = new QVariant(variant); } Value::Value(const QList& array, ValueType type) : d(new Private) { Q_ASSERT(type == OrderedArray || type == UnorderedArray || type == AlternativeArray || type == LangArray); d->value.array = new QList(array); d->type = type; // TODO: I am hesitating about LangArray to keep them as array or convert them to maps } Value::Value(const QMap& structure) : d(new Private) { d->type = Structure; d->value.structure = new QMap(structure); } Value::Value(const KisMetaData::Rational& signedRational) : d(new Private) { d->type = Value::Rational; d->value.rational = new KisMetaData::Rational(signedRational); } Value::Value(const Value& v) : d(new Private) { d->type = Invalid; *this = v; } Value& Value::operator=(const Value & v) { d->type = v.d->type; d->propertyQualifiers = v.d->propertyQualifiers; switch (d->type) { case Invalid: break; case Variant: d->value.variant = new QVariant(*v.d->value.variant); break; case OrderedArray: case UnorderedArray: case AlternativeArray: case LangArray: d->value.array = new QList(*v.d->value.array); break; case Structure: d->value.structure = new QMap(*v.d->value.structure); break; case Rational: d->value.rational = new KisMetaData::Rational(*v.d->value.rational); } return *this; } Value::~Value() { delete d; } void Value::addPropertyQualifier(const QString& _name, const Value& _value) { d->propertyQualifiers[_name] = _value; } const QMap& Value::propertyQualifiers() const { return d->propertyQualifiers; } Value::ValueType Value::type() const { return d->type; } double Value::asDouble() const { switch (type()) { case Variant: return d->value.variant->toDouble(0); case Rational: return d->value.rational->numerator / (double)d->value.rational->denominator; default: return 0.0; } return 0.0; } int Value::asInteger() const { switch (type()) { case Variant: return d->value.variant->toInt(0); case Rational: return d->value.rational->numerator / d->value.rational->denominator; default: return 0; } return 0; } QVariant Value::asVariant() const { switch (type()) { case Variant: return *d->value.variant; case Rational: return QVariant(QString("%1 / %2").arg(d->value.rational->numerator).arg(d->value.rational->denominator)); default: break; } return QVariant(); } bool Value::setVariant(const QVariant& variant) { switch (type()) { case KisMetaData::Value::Invalid: *this = KisMetaData::Value(variant); return true; case Rational: { QRegExp rx("([^\\/]*)\\/([^\\/]*)"); rx.indexIn(variant.toString()); } case KisMetaData::Value::Variant: { if (d->value.variant->type() == variant.type()) { *d->value.variant = variant; return true; } } return true; default: break; } return false; } bool Value::setStructureVariant(const QString& fieldNAme, const QVariant& variant) { if (type() == Structure) { return (*d->value.structure)[fieldNAme].setVariant(variant); } return false; } bool Value::setArrayVariant(int index, const QVariant& variant) { if (isArray()) { for (int i = d->value.array->size(); i <= index; ++i) { d->value.array->append(Value()); } (*d->value.array)[index].setVariant(variant); } return false; } KisMetaData::Rational Value::asRational() const { if (d->type == Rational) { return *d->value.rational; } return KisMetaData::Rational(); } QList Value::asArray() const { if (isArray()) { return *d->value.array; } return QList(); } bool Value::isArray() const { return type() == OrderedArray || type() == UnorderedArray || type() == AlternativeArray; } QMap Value::asStructure() const { if (type() == Structure) { return *d->value.structure; } return QMap(); } QDebug operator<<(QDebug debug, const Value &v) { switch (v.type()) { case Value::Invalid: debug.nospace() << "invalid value"; break; case Value::Variant: debug.nospace() << "Variant: " << v.asVariant(); break; case Value::OrderedArray: case Value::UnorderedArray: case Value::AlternativeArray: case Value::LangArray: debug.nospace() << "Array: " << v.asArray(); break; case Value::Structure: debug.nospace() << "Structure: " << v.asStructure(); break; case Value::Rational: debug.nospace() << "Rational: " << v.asRational().numerator << " / " << v.asRational().denominator; break; } return debug.space(); } bool Value::operator==(const Value& rhs) const { if (d->type != rhs.d->type) return false; switch (d->type) { case Value::Invalid: return true; case Value::Variant: return asVariant() == rhs.asVariant(); case Value::OrderedArray: case Value::UnorderedArray: case Value::AlternativeArray: case Value::LangArray: return asArray() == rhs.asArray(); case Value::Structure: return asStructure() == rhs.asStructure(); case Value::Rational: return asRational() == rhs.asRational(); } return false; } Value& Value::operator+=(const Value & v) { switch (d->type) { case Value::Invalid: Q_ASSERT(v.type() == Value::Invalid); break; case Value::Variant: Q_ASSERT(v.type() == Value::Variant); { QVariant v1 = *d->value.variant; QVariant v2 = *v.d->value.variant; Q_ASSERT(v2.canConvert(v1.type())); switch (v1.type()) { default: + warnImage << "KisMetaData: Merging metadata of type" << v1.type() << "is unsupported!"; + break; + case QVariant::Date: + *d->value.variant = qMax(v1.toDate(), v2.toDate()); + break; + case QVariant::DateTime: + *d->value.variant = qMax(v1.toDate(), v2.toDate()); break; - case QVariant::Date: { - QDate date; - date.fromJulianDay(v1.toDate().toJulianDay() - + v2.toDate().toJulianDay()); - *d->value.variant = date; - } - break; - case QVariant::DateTime: { - QDateTime dt; -#if QT_VERSION >= 0x050900 - dt.fromSecsSinceEpoch( - v1.toDateTime().toSecsSinceEpoch() - + v2.toDateTime().toSecsSinceEpoch()); -#else - dt.fromTime_t( - v1.toDateTime().toTime_t() - + v2.toDateTime().toTime_t()); -#endif - *d->value.variant = dt; - } - break; case QVariant::Double: *d->value.variant = v1.toDouble() + v2.toDouble(); break; case QVariant::Int: *d->value.variant = v1.toInt() + v2.toInt(); break; case QVariant::List: *d->value.variant = v1.toList() + v2.toList(); break; case QVariant::LongLong: *d->value.variant = v1.toLongLong() + v2.toLongLong(); break; case QVariant::Point: *d->value.variant = v1.toPoint() + v2.toPoint(); break; case QVariant::PointF: *d->value.variant = v1.toPointF() + v2.toPointF(); break; case QVariant::String: *d->value.variant = QVariant(v1.toString() + v2.toString()); break; case QVariant::StringList: *d->value.variant = v1.toStringList() + v2.toStringList(); break; case QVariant::Time: { QTime t1 = v1.toTime(); QTime t2 = v2.toTime(); int h = t1.hour() + t2.hour(); int m = t1.minute() + t2.minute(); int s = t1.second() + t2.second(); int ms = t1.msec() + t2.msec(); if (ms > 999) { ms -= 999; s++; } if (s > 60) { s -= 60; m++; } if (m > 60) { m -= 60; h++; } if (h > 24) { h -= 24; } *d->value.variant = QTime(h, m, s, ms); } break; case QVariant::UInt: *d->value.variant = v1.toUInt() + v2.toUInt(); break; case QVariant::ULongLong: *d->value.variant = v1.toULongLong() + v2.toULongLong(); break; } } break; case Value::OrderedArray: case Value::UnorderedArray: case Value::AlternativeArray: { if (v.isArray()) { *(d->value.array) += *(v.d->value.array); } else { d->value.array->append(v); } } break; case Value::LangArray: { Q_ASSERT(v.type() == Value::LangArray); } break; case Value::Structure: { Q_ASSERT(v.type() == Value::Structure); break; } case Value::Rational: { Q_ASSERT(v.type() == Value::Rational); d->value.rational->numerator = (d->value.rational->numerator * v.d->value.rational->denominator) + (v.d->value.rational->numerator * d->value.rational->denominator); d->value.rational->denominator *= v.d->value.rational->denominator; break; } } return *this; } QMap Value::asLangArray() const { Q_ASSERT(d->type == LangArray); QMap langArray; Q_FOREACH (const KisMetaData::Value& val, *d->value.array) { Q_ASSERT(val.d->propertyQualifiers.contains("xml:lang")); // TODO propably worth to have an assert for this in the constructor as well KisMetaData::Value valKeyVal = val.d->propertyQualifiers.value("xml:lang"); Q_ASSERT(valKeyVal.type() == Variant); QVariant valKeyVar = valKeyVal.asVariant(); Q_ASSERT(valKeyVar.type() == QVariant::String); langArray[valKeyVar.toString()] = val; } return langArray; } QString Value::toString() const { switch (type()) { case Value::Invalid: return i18n("Invalid value."); case Value::Variant: return d->value.variant->toString(); break; case Value::OrderedArray: case Value::UnorderedArray: case Value::AlternativeArray: case Value::LangArray: { QString r = QString("[%1]{ ").arg(d->value.array->size()); for (int i = 0; i < d->value.array->size(); ++i) { const Value& val = d->value.array->at(i); r += val.toString(); if (i != d->value.array->size() - 1) { r += ','; } r += ' '; } r += '}'; return r; } case Value::Structure: { QString r = "{ "; QList fields = d->value.structure->keys(); for (int i = 0; i < fields.count(); ++i) { const QString& field = fields[i]; const Value& val = d->value.structure->value(field); r += field + " => " + val.toString(); if (i != d->value.array->size() - 1) { r += ','; } r += ' '; } r += '}'; return r; } break; case Value::Rational: return QString("%1 / %2").arg(d->value.rational->numerator).arg(d->value.rational->denominator); } return i18n("Invalid value."); } diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h index 20997d552a..dc7ef8db0c 100644 --- a/libs/pigment/KoColorSpace.h +++ b/libs/pigment/KoColorSpace.h @@ -1,645 +1,645 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACE_H #define KOCOLORSPACE_H #include #include #include #include #include #include #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include "KoColorProofingConversionTransformation.h" #include "KoCompositeOp.h" #include #include "kritapigment_export.h" class QDomDocument; class QDomElement; class KoChannelInfo; class KoColorProfile; class KoColorTransformation; class QBitArray; enum Deletability { OwnedByRegistryDoNotDelete, OwnedByRegistryRegistryDeletes, NotOwnedByRegistry }; enum ColorSpaceIndependence { FULLY_INDEPENDENT, TO_LAB16, TO_RGBA8, TO_RGBA16 }; class KoMixColorsOp; class KoConvolutionOp; /** * A KoColorSpace is the definition of a certain color space. * * A color model and a color space are two related concepts. A color * model is more general in that it describes the channels involved and * how they in broad terms combine to describe a color. Examples are * RGB, HSV, CMYK. * * A color space is more specific in that it also describes exactly how * the channels are combined. So for each color model there can be a * number of specific color spaces. So RGB is the model and sRGB, * adobeRGB, etc are colorspaces. * * In Pigment KoColorSpace acts as both a color model and a color space. * You can think of the class definition as the color model, but the * instance of the class as representing a colorspace. * * A third concept is the profile represented by KoColorProfile. It * represents the info needed to specialize a color model into a color * space. * * KoColorSpace is an abstract class serving as an interface. * * Subclasses implement actual color spaces * Some subclasses implement only some parts and are named Traits * */ class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable { friend class KoColorSpaceRegistry; friend class KoColorSpaceFactory; protected: /// Only for use by classes that serve as baseclass for real color spaces KoColorSpace(); public: /// Should be called by real color spaces KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp); virtual bool operator==(const KoColorSpace& rhs) const; protected: virtual ~KoColorSpace(); public: //========== Gamut and other basic info ===================================// /* * @returns QPolygonF with 5*channel samples converted to xyY. * maybe convert to 3d space in future? */ QPolygonF gamutXYY() const; /* * @returns a polygon with 5 samples per channel converted to xyY, but unlike * gamutxyY it focuses on the luminance. This then can be used to visualise * the approximate trc of a given colorspace. */ QPolygonF estimatedTRCXYY() const; QVector colorants() const; QVector lumaCoefficients() const; //========== Channels =====================================================// /// Return a list describing all the channels this color model has. The order /// of the channels in the list is the order of channels in the pixel. To find /// out the preferred display position, use KoChannelInfo::displayPosition. QList channels() const; /** * The total number of channels for a single pixel in this color model */ virtual quint32 channelCount() const = 0; /** * The total number of color channels (excludes alpha) for a single * pixel in this color model. */ virtual quint32 colorChannelCount() const = 0; /** * returns a QBitArray that contains true for the specified * channel types: * * @param color if true, set all color channels to true * @param alpha if true, set all alpha channels to true * * The order of channels is the colorspace descriptive order, * not the pixel order. */ QBitArray channelFlags(bool color = true, bool alpha = false) const; /** * The size in bytes of a single pixel in this color model */ virtual quint32 pixelSize() const = 0; /** * Return a string with the channel's value suitable for display in the gui. */ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a string with the channel's value with integer * channels normalised to the floating point range 0 to 1, if * appropriate. */ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a QVector of floats with channels' values normalized * to floating point range 0 to 1. */ virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const = 0; /** * Write in the pixel the value from the normalized vector. */ virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const = 0; /** * Convert the value of the channel at the specified position into * an 8-bit value. The position is not the number of bytes, but * the position of the channel as defined in the channel info list. */ virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0; /** * Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels * should be set to whatever makes sense for 'empty' channels of this color space, * with the intent being that the pixel should look like it only has the given channel. */ virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0; //========== Identification ===============================================// /** * ID for use in files and internally: unchanging name. As the id must be unique * it is usually the concatenation of the id of the color model and of the color * depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f". */ QString id() const; /** * User visible name which contains the name of the color model and of the color depth. * For intance "RGBA (8-bits)" or "CMYKA (16-bits)". */ QString name() const; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @return true if the profile given in argument can be used by this color space */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * If false, images in this colorspace will degrade considerably by * functions, tools and filters that have the given measure of colorspace * independence. * * @param independence the measure to which this colorspace will suffer * from the manipulations of the tool or filter asking * @return false if no degradation will take place, true if degradation will * take place */ virtual bool willDegrade(ColorSpaceIndependence independence) const = 0; //========== Capabilities =================================================// /** * Tests if the colorspace offers the specific composite op. */ virtual bool hasCompositeOp(const QString & id) const; /** * Returns the list of user-visible composite ops supported by this colorspace. */ virtual QList compositeOps() const; /** * Retrieve a single composite op from the ones this colorspace offers. * If the requeste composite op does not exist, COMPOSITE_OVER is returned. */ - virtual const KoCompositeOp * compositeOp(const QString & id) const; + const KoCompositeOp * compositeOp(const QString & id) const; /** * add a composite op to this colorspace. */ virtual void addCompositeOp(const KoCompositeOp * op); /** * Returns true if the colorspace supports channel values outside the * (normalised) range 0 to 1. */ virtual bool hasHighDynamicRange() const = 0; //========== Display profiles =============================================// /** * Return the profile of this color space. */ virtual const KoColorProfile * profile() const = 0; //================= Conversion functions ==================================// /** * The fromQColor methods take a given color defined as an RGB QColor * and fills a byte array with the corresponding color in the * the colorspace managed by this strategy. * * @param color the QColor that will be used to fill dst * @param dst a pointer to a pixel * @param profile the optional profile that describes the color values of QColor */ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0; /** * The toQColor methods take a byte array that is at least pixelSize() long * and converts the contents to a QColor, using the given profile as a source * profile and the optional profile as a destination profile. * * @param src a pointer to the source pixel * @param c the QColor that will be filled with the color at src * @param profile the optional profile that describes the color in c, for instance the monitor profile */ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0; /** * Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles. * * @param data A pointer to a contiguous memory region containing width * height pixels * @param width in pixels * @param height in pixels * @param dstProfile destination profile * @param renderingIntent the rendering intent */ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from Lab (D50). to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit lab format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit rgb format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Create a color conversion transformation. */ virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert a byte array of srcLen pixels *src to the specified color space * and put the converted bytes into the prepared byte array *dst. * * Returns false if the conversion failed, true if it succeeded * * This function is not thread-safe. If you want to apply multiple conversion * in different threads at the same time, you need to create one color converter * per-thread using createColorConverter. */ virtual bool convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const; /** * @brief proofPixelsTo * @param src * @param dst * @param dstColorSpace the colorspace to which we go to. * @param proofingSpace the proofing space. * @param numPixels the amount of pixels. * @param renderingIntent the rendering intent used for rendering. * @param proofingIntent the intent used for proofing. * @param conversionFlags the conversion flags. * @param gamutWarning the data() of a KoColor. * @param adaptationState the state of adaptation, only affects absolute colorimetric. * @return */ virtual bool proofPixelsTo(const quint8 * src, quint8 * dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const; //============================== Manipulation functions ==========================// // // The manipulation functions have default implementations that _convert_ the pixel // to a QColor and back. Reimplement these methods in your color strategy! // /** * Get the alpha value of the given pixel, downscaled to an 8-bit value. */ virtual quint8 opacityU8(const quint8 * pixel) const = 0; virtual qreal opacityF(const quint8 * pixel) const = 0; /** * Set the alpha channel of the given run of pixels to the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0; /** * Multiply the alpha channel of the given run of pixels by the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; /** * Applies the specified 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Applies the inverted specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Create an adjustment object for adjusting the brightness and contrast * transferValues is a 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0; /** * Create an adjustment object for adjusting individual channels * transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. * * The layout of the channels must be the following: * * 0..N-2 - color channels of the pixel; * N-1 - alpha channel of the pixel (if exists) */ virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0; /** * Darken all color channels with the given amount. If compensate is true, * the compensation factor will be used to limit the darkening. * */ virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0; /** * Invert color channels of the given pixels * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createInvertTransformation() const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). Only completely * opaque and completely transparent are taken into account when computing the different; * other transparency levels are not regarded when finding the difference. */ virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). This function * takes the Alpha channel of the pixel into account. Alpha channel has the same * weight as Lightness channel. */ virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0; /** * @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoMixColorsOp* mixColorsOp() const; /** * @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoConvolutionOp* convolutionOp() const; /** * Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible */ virtual quint8 intensity8(const quint8 * src) const = 0; /* *increase luminosity by step */ virtual void increaseLuminosity(quint8 * pixel, qreal step) const; virtual void decreaseLuminosity(quint8 * pixel, qreal step) const; virtual void increaseSaturation(quint8 * pixel, qreal step) const; virtual void decreaseSaturation(quint8 * pixel, qreal step) const; virtual void increaseHue(quint8 * pixel, qreal step) const; virtual void decreaseHue(quint8 * pixel, qreal step) const; virtual void increaseRed(quint8 * pixel, qreal step) const; virtual void increaseGreen(quint8 * pixel, qreal step) const; virtual void increaseBlue(quint8 * pixel, qreal step) const; virtual void increaseYellow(quint8 * pixel, qreal step) const; virtual void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0; virtual QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0; virtual void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const = 0; virtual QVector fromYUV(qreal *y, qreal *u, qreal *v) const = 0; /** * Compose two arrays of pixels together. If source and target * are not the same color model, the source pixels will be * converted to the target model. We're "dst" -- "dst" pixels are always in _this_ * colorspace. * * @param srcSpace the colorspace of the source pixels that will be composited onto "us" * @param param the information needed for blitting e.g. the source and destination pixel data, * the opacity and flow, ... * @param op the composition operator to use, e.g. COPY_OVER * */ virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Serialize this color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * This function doesn't create the element but rather the , * , ... elements. It is assumed that colorElt is the * element. * * @param pixel buffer to serialized * @param colorElt root element for the serialization, it is assumed that this * element is * @param doc is the document containing colorElt */ virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0; /** * Unserialize a color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * @param pixel buffer where the color will be unserialized * @param elt the element to unserialize (, , ) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0; KoColorTransformation* createColorTransformation(const QString & id, const QHash & parameters) const; protected: /** * Use this function in the constructor of your colorspace to add the information about a channel. * @param ci a pointer to the information about a channel */ virtual void addChannel(KoChannelInfo * ci); const KoColorConversionTransformation* toLabA16Converter() const; const KoColorConversionTransformation* fromLabA16Converter() const; const KoColorConversionTransformation* toRgbA16Converter() const; const KoColorConversionTransformation* fromRgbA16Converter() const; /** * Returns the thread-local conversion cache. If it doesn't exist * yet, it is created. If it is currently too small, it is resized. */ QVector * threadLocalConversionCache(quint32 size) const; /** * This function defines the behavior of the bitBlt function * when the composition of pixels in different colorspaces is * requested, that is in case: * * srcCS == any * dstCS == this * * 1) preferCompositionInSourceColorSpace() == false, * * the source pixels are first converted to *this color space * and then composition is performed. * * 2) preferCompositionInSourceColorSpace() == true, * * the destination pixels are first converted into *srcCS color * space, then the composition is done, and the result is finally * converted into *this colorspace. * * This is used by alpha8() color space mostly, because it has * weaker representation of the color, so the composition * should be done in CS with richer functionality. */ virtual bool preferCompositionInSourceColorSpace() const; struct Private; Private * const d; }; inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs) { if (cs) { dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )"; } else { dbg.nospace() << "0x0"; } return dbg.space(); } #endif // KOCOLORSPACE_H diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index ccbe871f0a..7519eeac96 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,577 +1,579 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/kis_dlg_internal_color_selector.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc kis_base_option.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp kis_painting_assistants_manager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp kis_script_manager.cpp kis_resource_server_provider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp kis_view_plugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp kis_fps_decoration.cpp recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_recording_adapter.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_gradient_slider_widget.cc widgets/kis_gradient_slider.cpp widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp + widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/squeezedcombobox.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_spinbox_color_selector.cpp widgets/kis_screen_color_picker.cpp widgets/kis_preset_live_preview_view.cpp widgets/KoDualColorButton.cpp widgets/kis_color_input.cpp widgets/kis_color_button.cpp widgets/KisVisualColorSelector.cpp widgets/KisVisualColorSelectorShape.cpp widgets/KisVisualEllipticalSelectorShape.cpp widgets/KisVisualRectangleSelectorShape.cpp widgets/KisVisualTriangleSelectorShape.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KoShapeFillWrapper.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoStackAction.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp ) if(WIN32) if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS) message(FATAL_ERROR "Qt5Gui Private header are missing!") endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp ) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_x11.cpp input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui + forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 3bf41e75cd..43aa6bc65a 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,808 +1,812 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplicationPrivate { public: KisApplicationPrivate() : splashScreen(0) {} QPointer splashScreen; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (m_fileCount > 0 || hideSplash) { m_splash->hide(); } else { m_splash->setWindowFlags(Qt::Dialog); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); m_splash->setParent(0); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } m_splash->show(); m_splash->activateWindow(); } } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new KisApplicationPrivate) , m_autosaveDialog(0) , m_mainWindow(0) , m_batchRun(false) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); + KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); + d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); + d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); + d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); // Indicate that it is now safe for users of KoResourcePaths to load resources KoResourcePaths::setReady(); } void KisApplication::loadResources() { setSplashScreenLoadingText(i18n("Loading Gradients...")); processEvents(); KoResourceServerProvider::instance()->gradientServer(true); // Load base resources setSplashScreenLoadingText(i18n("Loading Patterns...")); processEvents(); KoResourceServerProvider::instance()->patternServer(true); setSplashScreenLoadingText(i18n("Loading Palettes...")); processEvents(); KoResourceServerProvider::instance()->paletteServer(false); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(true); // load paintop presets setSplashScreenLoadingText(i18n("Loading Paint Operations...")); processEvents(); KisResourceServerProvider::instance()->paintOpPresetServer(true); // load symbols setSplashScreenLoadingText(i18n("Loading SVG Symbol Collections...")); processEvents(); KoResourceServerProvider::instance()->svgSymbolCollectionServer(true); setSplashScreenLoadingText(i18n("Loading Resource Bundles...")); processEvents(); KisResourceServerProvider::instance()->resourceBundleServer(); } void KisApplication::loadPlugins() { KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg; #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool print = args.print(); const bool exportAs = args.exportAs(); const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); // print & exportAsPdf do user interaction ATM const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed // TODO: fix print & exportAsPdf to work without mainwindow shown const bool showmainWindow = !exportAs; // would be !batchRun; const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); // Initialize all Krita directories etc. KoGlobal::initialize(); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load all resources and tags before the plugins do that loadResources(); // Load the plugins loadPlugins(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); m_mainWindow = KisPart::instance()->createMainWindow(); if (showmainWindow) { m_mainWindow->initializeGeometry(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { m_mainWindow->restoreWorkspace(workspace->dockerState()); } } if (args.canvasOnly()) { m_mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { m_mainWindow->showFullScreen(); } else { m_mainWindow->show(); } } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) if (!m_batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { KisPart::instance()->addDocument(doc); m_mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (m_batchRun) { continue; } if (createNewDocFromTemplate(fileName, m_mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } else if (m_mainWindow) { KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (m_mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { if (print) { m_mainWindow->slotFilePrint(); nPrinted++; // TODO: trigger closing of app once printing is done } else if (exportAsPdf) { KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName); if (job) connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow, SLOT(slotFileQuit()), Qt::QueuedConnection); nPrinted++; } else { // Normal case, success numberOfOpenDocuments++; } } else { // .... if failed // delete doc; done by openDocument } } } } if (m_batchRun) { return nPrinted > 0; } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(); d->splashScreen->displayRecentFiles(); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { delete d; } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (m_batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } m_autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = m_autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (m_mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; m_mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete m_autosaveDialog; m_autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); KisMainWindow::OpenFlags batchFlags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 27d7bd684d..365d477a81 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2488 +1,2491 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "KisDocument.h" #include "KisDocument.h" #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_resource_server_provider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); dockWidget->setTabEnabled(false); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent) : q(parent) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; bool noCleanup {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; + KisAction *fullScreenMode {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QString lastExportLocation; QMap dockWidgetsMap; QMap dockWidgetVisibilityMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow() : KXmlGuiWindow() , d(new Private(this)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(false); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); KisConfig cfg; d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setMainWindow(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->mdiArea); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); setAutoSaveSettings("krita", false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } void KisMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); if (d->noCleanup) return; delete d->viewManager; delete d; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg; subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisUpdateSchedulerConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) if (path.contains(*it)) ok = false; // it's in the tmp resource #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) { /** * FIXME: this is a hacking approach of reloading the updated recent files list. * Sometimes, the result of reading from KConfig right after doing 'sync()' still * returns old values of the recent files. Reading the same files a bit later * returns correct "updated" files. I couldn't find the cause of it (DK). */ KisMainWindow *mw = static_cast(window); if (mw != this) { mw->reloadRecentFileList(); } } } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // new documents aren't saved yet, so we don't need to say it is modified // new files don't have a URL, so we are using that for the check if (!doc->url().isEmpty()) { if ( doc->isModified()) { caption += " [" + i18n("Modified") + "] "; } } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KisStatusBar::formatSize(m_fileSizeStats.imageSize)).append( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!flags && BatchMode) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile()); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (ret && !isExporting) { document->setRecovered(false); } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->undoAction()->trigger(); d->undo->setText(activeView()->undoAction()->text()); } } void KisMainWindow::redo() { if (activeView()) { activeView()->redoAction()->trigger(); d->redo->setText(activeView()->redoAction()->text()); } } void KisMainWindow::closeEvent(QCloseEvent *e) { d->mdiArea->closeAllSubWindows(); QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); { KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); } QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if (d->noCleanup) return; if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text()); actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text()); d->viewManager->setCurrentView(view); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { openDocument(url, None); } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); KisConfig cfg; int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } bool KisMainWindow::restoreWorkspace(const QByteArray &state) { QByteArray oldState = saveState(); const bool showTitlebars = KisConfig().showDockerTitleBars(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->hide(); dock->titleBarWidget()->setVisible(showTitlebars); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); } } return false; } Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed)); } } return success; } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { if(!slotFileCloseAll()) return; close(); Q_FOREACH (QPointer mainWin, KisPart::instance()->mainWindows()) { if (mainWin != this) { if(!mainWin->slotFileCloseAll()) return; mainWin->close(); } } } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("krita")); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg; cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } KoDockWidgetTitleBar *titleBar = dynamic_cast(dockWidget->titleBarWidget()); // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } titleBar->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); //dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar; if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer(false)->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); auto ds = w->dockerState(); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(ds); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg; QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisPart::instance()->createMainWindow()->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointerKisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); KisConfig cfg; actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); - actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); + d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); d->toggleDockers = actionManager->createAction("view_toggledockers"); cfg.showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); + + d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::showDockerTitleBars(bool show) { Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed)); } } KisConfig cfg; cfg.setShowDockerTitleBars(show); } void KisMainWindow::moveEvent(QMoveEvent *e) { if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisTemplateCreateDia.cpp b/libs/ui/KisTemplateCreateDia.cpp index 81084a9af2..6d8a1883ca 100644 --- a/libs/ui/KisTemplateCreateDia.cpp +++ b/libs/ui/KisTemplateCreateDia.cpp @@ -1,518 +1,530 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer 2000 Werner Trobin Copyright (C) 2004 Nicolas GOUTTE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #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 // ODF thumbnail extent static const int thumbnailExtent = 128; class KisTemplateCreateDiaPrivate { public: KisTemplateCreateDiaPrivate(const QString &templatesResourcePath, const QString &filePath, const QPixmap &thumbnail) : m_tree(templatesResourcePath, true) , m_filePath(filePath) , m_thumbnail(thumbnail) { } KisTemplateTree m_tree; QLineEdit *m_name; QRadioButton *m_default; QRadioButton *m_custom; QPushButton *m_select; QLabel *m_preview; QString m_customFile; QPixmap m_customPixmap; QTreeWidget *m_groups; QPushButton *m_add; QPushButton *m_remove; QCheckBox *m_defaultTemplate; QString m_filePath; QPixmap m_thumbnail; bool m_changed; }; /**************************************************************************** * * Class: KisTemplateCreateDia * ****************************************************************************/ KisTemplateCreateDia::KisTemplateCreateDia(const QString &templatesResourcePath, const QString &filePath, const QPixmap &thumbnail, QWidget *parent) - : KoDialog(parent) - , d(new KisTemplateCreateDiaPrivate(templatesResourcePath, filePath, thumbnail)) + : KoDialog(parent) + , d(new KisTemplateCreateDiaPrivate(templatesResourcePath, filePath, thumbnail)) { setButtons( KoDialog::Ok|KoDialog::Cancel ); setDefaultButton( KoDialog::Ok ); setCaption( i18n( "Create Template" ) ); setModal( true ); setObjectName( "template create dia" ); QWidget *mainwidget = mainWidget(); QHBoxLayout *mbox=new QHBoxLayout( mainwidget ); QVBoxLayout* leftbox = new QVBoxLayout(); mbox->addLayout( leftbox ); QLabel *label=new QLabel(i18nc("Template name", "Name:"), mainwidget); QHBoxLayout *namefield=new QHBoxLayout(); leftbox->addLayout( namefield ); namefield->addWidget(label); d->m_name=new QLineEdit(mainwidget); d->m_name->setFocus(); connect(d->m_name, SIGNAL(textChanged(const QString &)), this, SLOT(slotNameChanged(const QString &))); namefield->addWidget(d->m_name); label=new QLabel(i18n("Group:"), mainwidget); leftbox->addWidget(label); d->m_groups = new QTreeWidget(mainwidget); leftbox->addWidget(d->m_groups); d->m_groups->setColumnCount(1); d->m_groups->setHeaderHidden(true); d->m_groups->setRootIsDecorated(true); d->m_groups->setSortingEnabled(true); fillGroupTree(); d->m_groups->sortItems(0, Qt::AscendingOrder); QHBoxLayout *bbox=new QHBoxLayout(); leftbox->addLayout( bbox ); d->m_add=new QPushButton(i18n("&Add Group..."), mainwidget); connect(d->m_add, SIGNAL(clicked()), this, SLOT(slotAddGroup())); bbox->addWidget(d->m_add); d->m_remove=new QPushButton(i18n("&Remove"), mainwidget); connect(d->m_remove, SIGNAL(clicked()), this, SLOT(slotRemove())); bbox->addWidget(d->m_remove); QVBoxLayout *rightbox=new QVBoxLayout(); mbox->addLayout( rightbox ); QGroupBox *pixbox = new QGroupBox(i18n("Picture"), mainwidget); rightbox->addWidget(pixbox); QVBoxLayout *pixlayout=new QVBoxLayout(pixbox ); d->m_default=new QRadioButton(i18n("&Preview"), pixbox); d->m_default->setChecked(true); connect(d->m_default, SIGNAL(clicked()), this, SLOT(slotDefault())); pixlayout->addWidget(d->m_default); QHBoxLayout *custombox=new QHBoxLayout(); d->m_custom=new QRadioButton(i18n("Custom:"), pixbox); d->m_custom->setChecked(false); connect(d->m_custom, SIGNAL(clicked()), this, SLOT(slotCustom())); custombox->addWidget(d->m_custom); d->m_select=new QPushButton(i18n("&Select..."), pixbox); connect(d->m_select, SIGNAL(clicked()), this, SLOT(slotSelect())); custombox->addWidget(d->m_select); custombox->addStretch(1); pixlayout->addLayout(custombox); d->m_preview=new QLabel(pixbox); // setPixmap() -> auto resize? pixlayout->addWidget(d->m_preview, 0, Qt::AlignCenter); pixlayout->addStretch(1); d->m_defaultTemplate = new QCheckBox( i18n("Use the new template as default"), mainwidget ); d->m_defaultTemplate->setChecked( true ); d->m_defaultTemplate->setVisible( false ); d->m_defaultTemplate->setToolTip(i18n("Use the new template every time Krita starts")); rightbox->addWidget( d->m_defaultTemplate ); enableButtonOk(false); d->m_changed=false; updatePixmap(); connect(d->m_groups, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged())); d->m_remove->setEnabled(d->m_groups->currentItem()); connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); } KisTemplateCreateDia::~KisTemplateCreateDia() { delete d; } void KisTemplateCreateDia::slotSelectionChanged() { const QTreeWidgetItem* item = d->m_groups->currentItem(); d->m_remove->setEnabled( item ); if ( ! item ) return; if ( item->parent() != 0 ) { d->m_name->setText( item->text( 0 ) ); } } void KisTemplateCreateDia::createTemplate(const QString &templatesResourcePath, - const char *suffix, - KisDocument *document, QWidget *parent) + const char *suffix, + KisDocument *document, QWidget *parent) { Q_UNUSED(suffix); QString fileName; { QTemporaryFile tempFile; if (!tempFile.open()) { qWarning("Creation of temporary file to store template failed."); return; } fileName = tempFile.fileName(); } - bool retval = document->exportDocumentSync(QUrl::fromLocalFile(fileName), document->mimeType()); + + bool retval = document->exportDocumentSync(QUrl::fromLocalFile(fileName), KisDocument::nativeFormatMimeType()); if (!retval) { qWarning("Could not save template"); return; } const QPixmap thumbnail = document->generatePreview(QSize(thumbnailExtent, thumbnailExtent)); KisTemplateCreateDia *dia = new KisTemplateCreateDia(templatesResourcePath, fileName, thumbnail, parent); dia->exec(); delete dia; QDir d; d.remove(fileName); } -static void -saveAsQuadraticPng(const QPixmap &pixmap, const QString &fileName) +static void saveAsQuadraticPng(const QPixmap &pixmap, const QString &fileName) { QImage icon = pixmap.toImage(); icon = icon.convertToFormat(QImage::Format_ARGB32); const int iconExtent = qMax(icon.width(), icon.height()); icon = icon.copy((icon.width() - iconExtent) / 2, (icon.height() - iconExtent) / 2, iconExtent, iconExtent); icon.save(fileName, "PNG"); } void KisTemplateCreateDia::slotOk() { // get the current item, if there is one... QTreeWidgetItem *item = d->m_groups->currentItem(); - if(!item) + if (!item) item = d->m_groups->topLevelItem(0); - if(!item) { // safe :) + if (!item) { // safe :) d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } // is it a group or a template? anyway - get the group :) - if(item->parent() != 0) + if (item->parent() != 0) item=item->parent(); - if(!item) { // *very* safe :P + if (!item) { // *very* safe :P d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } KisTemplateGroup *group=d->m_tree.find(item->text(0)); - if(!group) { // even safer + if (!group) { // even safer d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } - if(d->m_name->text().isEmpty()) { + if (d->m_name->text().isEmpty()) { d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } // copy the tmp file and the picture the app provides QString dir = KoResourcePaths::saveLocation("data", d->m_tree.templatesResourcePath()); dir += group->name(); QString templateDir = dir+"/.source/"; QString iconDir = dir+"/.icon/"; QString file = KisTemplates::trimmed(d->m_name->text()); QString tmpIcon = ".icon/"+file; tmpIcon += ".png"; QString icon=iconDir+file; icon += ".png"; QString ext = ".kra"; QString dest = templateDir + file + ext; if (QFile::exists(dest)) { do { file = file.prepend( '_' ); dest = templateDir + file + ext; tmpIcon=".icon/" + file + ".png"; icon=iconDir + file + ".png"; } while (QFile(dest).exists()); } bool ignore = false; KisTemplate *t = new KisTemplate(d->m_name->text(), QString(), ".source/"+ file + ext, tmpIcon, "", "", false, true); if (!group->add(t)) { KisTemplate *existingTemplate=group->find(d->m_name->text()); if (existingTemplate && !existingTemplate->isHidden()) { if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Do you really want to overwrite the existing '%1' template?", existingTemplate->name()), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { group->add(t, true); } else { delete t; return; } } else { ignore = true; } } QDir path; if (!path.mkpath(templateDir) || !path.mkpath(iconDir)) { d->m_tree.writeTemplateTree(); slotButtonClicked( KoDialog::Cancel ); return; } QString orig; orig = d->m_filePath; // don't overwrite the hidden template file with a new non-hidden one if (!ignore) { if (!QFile::copy(d->m_filePath, dest)) { qWarning() << "Could not copy" << d->m_filePath << "to" << dest; } // save the picture as icon - if(d->m_default->isChecked() && !d->m_thumbnail.isNull()) { + if (d->m_default->isChecked() && !d->m_thumbnail.isNull()) { saveAsQuadraticPng(d->m_thumbnail, icon); - } else if(!d->m_customPixmap.isNull()) { + } else if (!d->m_customPixmap.isNull()) { saveAsQuadraticPng(d->m_customPixmap, icon); } else { warnUI << "Could not save the preview picture!"; } } // if there's a .directory file, we copy this one, too bool ready=false; QStringList tmp=group->dirs(); for(QStringList::ConstIterator it=tmp.constBegin(); it!=tmp.constEnd() && !ready; ++it) { - if((*it).contains(dir)==0) { + if ((*it).contains(dir)==0) { orig = (*it) + ".directory"; // Check if we can read the file if (QFile(orig).exists()) { dest = dir + "/.directory"; // We copy the file with overwrite if (!QFile(orig).copy(dest)) { warnKrita << "Failed to copy from" << orig << "to" << dest; } ready = true; } } } d->m_tree.writeTemplateTree(); if ( d->m_defaultTemplate->isChecked() ) { - KConfigGroup grp( KSharedConfig::openConfig(), "TemplateChooserDialog"); - grp.writeEntry( "LastReturnType", "Template" ); - grp.writePathEntry( "FullTemplateName", dir + '/' + t->file() ); - grp.writePathEntry( "AlwaysUseTemplate", dir + '/' + t->file() ); + KConfigGroup grp( KSharedConfig::openConfig(), "TemplateChooserDialog"); + grp.writeEntry( "LastReturnType", "Template" ); + grp.writePathEntry( "FullTemplateName", dir + '/' + t->file() ); + grp.writePathEntry( "AlwaysUseTemplate", dir + '/' + t->file() ); } } void KisTemplateCreateDia::slotDefault() { d->m_default->setChecked(true); d->m_custom->setChecked(false); updatePixmap(); } void KisTemplateCreateDia::slotCustom() { d->m_default->setChecked(false); d->m_custom->setChecked(true); - if(d->m_customFile.isEmpty()) + if (d->m_customFile.isEmpty()) slotSelect(); else updatePixmap(); } void KisTemplateCreateDia::slotSelect() { d->m_default->setChecked(false); d->m_custom->setChecked(true); - // QT5TODO -// QString name = KIconDialog::getIcon(); -// if( name.isEmpty() ) { -// if(d->m_customFile.isEmpty()) { -// d->m_default->setChecked(true); -// d->m_custom->setChecked(false); -// } -// return; -// } - -// const QString path = KIconLoader::global()->iconPath(name, -thumbnailExtent); - d->m_customFile = QString();// path; + KoFileDialog dlg(this, KoFileDialog::OpenFile, "TemplateImages"); + dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); + dlg.setImageFilters(); + dlg.setCaption(i18n("Select an image")); + QString fn = dlg.filename(); + if (fn.isEmpty()) { + if (d->m_customFile.isEmpty()) { + d->m_default->setChecked(true); + d->m_custom->setChecked(false); + } + return; + } + QImage image(fn); + if (image.isNull()) { + QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); + } + d->m_customFile = fn; d->m_customPixmap = QPixmap(); updatePixmap(); } void KisTemplateCreateDia::slotNameChanged(const QString &name) { - if( ( name.trimmed().isEmpty() || !d->m_groups->topLevelItem(0) ) && !d->m_changed ) + if ( ( name.trimmed().isEmpty() || !d->m_groups->topLevelItem(0) ) && !d->m_changed ) enableButtonOk(false); else enableButtonOk(true); } void KisTemplateCreateDia::slotAddGroup() { const QString name = QInputDialog::getText(this, i18n("Add Group"), i18n("Enter group name:")); KisTemplateGroup *group = d->m_tree.find(name); if (group && !group->isHidden()) { QMessageBox::information( this, i18n("This name is already used."), i18n("Add Group") ); return; } QString dir = KoResourcePaths::saveLocation("data", d->m_tree.templatesResourcePath()); dir+=name; KisTemplateGroup *newGroup=new KisTemplateGroup(name, dir, 0, true); d->m_tree.add(newGroup); QTreeWidgetItem *item = new QTreeWidgetItem(d->m_groups, QStringList() << name); d->m_groups->setCurrentItem(item); d->m_groups->sortItems(0, Qt::AscendingOrder); d->m_name->setFocus(); enableButtonOk(true); d->m_changed=true; } void KisTemplateCreateDia::slotRemove() { QTreeWidgetItem *item = d->m_groups->currentItem(); - if(!item) + if (!item) return; QString what; - QString removed; - if (item->parent() == 0) { - what = i18n("Do you really want to remove that group?"); - removed = i18nc("@title:window", "Remove Group"); - } else { - what = i18n("Do you really want to remove that template?"); + QString removed; + if (item->parent() == 0) { + what = i18n("Do you really want to remove that group?"); + removed = i18nc("@title:window", "Remove Group"); + } else { + what = i18n("Do you really want to remove that template?"); removed = i18nc("@title:window", "Remove Template"); - } + } if (QMessageBox::warning(this, removed, what, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox:: No) { d->m_name->setFocus(); return; } - if(item->parent() == 0) { + if (item->parent() == 0) { KisTemplateGroup *group=d->m_tree.find(item->text(0)); - if(group) + if (group) group->setHidden(true); } else { bool done=false; QList groups = d->m_tree.groups(); QList::const_iterator it = groups.constBegin(); for(; it != groups.constEnd() && !done; ++it) { KisTemplate *t = (*it)->find(item->text(0)); - if(t) { + if (t) { t->setHidden(true); done=true; } } } delete item; item=0; enableButtonOk(true); d->m_name->setFocus(); d->m_changed=true; } void KisTemplateCreateDia::updatePixmap() { - if(d->m_default->isChecked() && !d->m_thumbnail.isNull()) + if (d->m_default->isChecked() && !d->m_thumbnail.isNull()) { d->m_preview->setPixmap(d->m_thumbnail); - else if(d->m_custom->isChecked() && !d->m_customFile.isEmpty()) { - if(d->m_customPixmap.isNull()) { + } + else if (d->m_custom->isChecked() && !d->m_customFile.isEmpty()) { + + if (d->m_customPixmap.isNull()) { dbgUI <<"Trying to load picture" << d->m_customFile; // use the code in KisTemplate to load the image... hacky, I know :) KisTemplate t("foo", "bar", QString(), d->m_customFile); - d->m_customPixmap=t.loadPicture(); + d->m_customPixmap = t.loadPicture(); } - else + else { warnUI << "Trying to load picture"; + } - if(!d->m_customPixmap.isNull()) + if (!d->m_customPixmap.isNull()) { d->m_preview->setPixmap(d->m_customPixmap); - else + } + else { d->m_preview->setText(i18n("Could not load picture.")); + } } - else + else { d->m_preview->setText(i18n("No picture available.")); + } } void KisTemplateCreateDia::fillGroupTree() { Q_FOREACH (KisTemplateGroup *group, d->m_tree.groups()) { - if(group->isHidden()) + if (group->isHidden()) continue; QTreeWidgetItem *groupItem=new QTreeWidgetItem(d->m_groups, QStringList() << group->name()); Q_FOREACH (KisTemplate *t, group->templates()) { - if(t->isHidden()) + if (t->isHidden()) continue; (void)new QTreeWidgetItem(groupItem, QStringList() << t->name()); } } } diff --git a/libs/ui/brushhud/kis_round_hud_button.cpp b/libs/ui/brushhud/kis_round_hud_button.cpp index 29bae7e147..d9d5ac2a5a 100644 --- a/libs/ui/brushhud/kis_round_hud_button.cpp +++ b/libs/ui/brushhud/kis_round_hud_button.cpp @@ -1,117 +1,117 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_round_hud_button.h" #include #include #include "kis_global.h" struct KisRoundHudButton::Private { Private() : isHighlighted(false) {} bool isHighlighted; QIcon onIcon; QIcon offIcon; }; KisRoundHudButton::KisRoundHudButton(QWidget *parent) : QAbstractButton(parent), m_d(new Private) { setMouseTracking(true); } KisRoundHudButton::~KisRoundHudButton() { } void KisRoundHudButton::setOnOffIcons(const QIcon &on, const QIcon &off) { m_d->onIcon = on; m_d->offIcon = off; } void KisRoundHudButton::paintEvent(QPaintEvent */*event*/) { const int borderWidth = 3; const QPointF center = QRectF(rect()).center(); const qreal radius = 0.5 * (center.x() + center.y()) - borderWidth; const QPen fgPen(palette().color(m_d->isHighlighted ? QPalette::Highlight : QPalette::WindowText), borderWidth); const QBrush bgBrush(palette().brush(isDown() || (isCheckable() && isChecked()) ? QPalette::Mid : QPalette::Window)); QPainter painter(this); painter.setPen(fgPen); painter.setBrush(bgBrush); painter.setRenderHints(QPainter::Antialiasing); painter.drawEllipse(center, radius, radius); if (!icon().isNull()) { const QIcon::Mode mode = isEnabled() ? QIcon::Normal : QIcon::Disabled; const QIcon::State state = isCheckable() && isChecked() ? QIcon::On : QIcon::Off; const QSize size = iconSize(); QPixmap pixmap = icon().pixmap(size, mode, state); QPointF iconOffset(0.5 * (width() - size.width()), 0.5 * (height() - size.height())); painter.drawPixmap(iconOffset, pixmap); } - if (!m_d->onIcon.isNull() && !m_d->onIcon.isNull()) { + if (!m_d->onIcon.isNull()) { const QIcon::Mode mode = isEnabled() ? QIcon::Normal : QIcon::Disabled; const QIcon icon = isCheckable() && isChecked() ? m_d->onIcon : m_d->offIcon; const QSize size = iconSize(); QPixmap pixmap = icon.pixmap(size, mode); QPointF iconOffset(0.5 * (width() - size.width()), 0.5 * (height() - size.height())); painter.drawPixmap(iconOffset, pixmap); } } bool KisRoundHudButton::hitButton(const QPoint &pos) const { const int borderWidth = 3; const QPointF center = QRectF(rect()).center(); const qreal radius = 0.5 * (center.x() + center.y()) - borderWidth; return kisDistance(center, pos) < radius; } void KisRoundHudButton::mouseMoveEvent(QMouseEvent *event) { m_d->isHighlighted = hitButton(event->pos()); QAbstractButton::mouseMoveEvent(event); } void KisRoundHudButton::leaveEvent(QEvent *event) { Q_UNUSED(event); m_d->isHighlighted = false; QAbstractButton::leaveEvent(event); } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index ed546469ad..f246047649 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1043 +1,1044 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_undo_adapter.h" #include "KisDocument.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" #include #include "opengl/kis_opengl_canvas_debugger.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceManager* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor updateSignalCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInCanvas; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; bool effectiveLodAllowedInCanvas() { return lodAllowedInCanvas && !bootstrapLodBlocked; } }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisLayer *layer = dynamic_cast(node.data()); if (layer) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } else { KisSelectionSP selection = layer->selection(); if (selection && selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0); shapeManager = shapeSelection->shapeManager(); } } } return shapeManager; } } KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); KisImageConfig config; m_d->updateSignalCompressor.setDelay(1000 / config.fpsLimit()); m_d->updateSignalCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), globalShapeManager(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg; const bool shouldShowDebugOverlay = (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) || cfg.enableBrushSpeedLogging(); if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); if (cfg.enableBrushSpeedLogging()) { connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) { m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag); disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(QWidget * widget) { KisAbstractCanvasWidget *tmp = dynamic_cast(widget); Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget"); if (m_d->popupPalette) { m_d->popupPalette->setParent(widget); } if(m_d->canvasWidget != 0) { tmp->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = tmp; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->setAutoFillBackground(false); widget->setAttribute(Qt::WA_OpaquePaintEvent); widget->setMouseTracking(true); widget->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller) { Q_ASSERT(controller->canvas() == this); controller->changeCanvasWidget(widget); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } KoShapeManager* KisCanvas2::shapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); // sanity check for consistency of the local shape manager KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) { localShapeManager = globalShapeManager(); } return localShapeManager ? localShapeManager : globalShapeManager(); } KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const { return &m_d->selectedShapesProxy; } KoShapeManager* KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg; m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg; QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL) { if (KisOpenGL::hasOpenGL()) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; createQPainterCanvas(); } } else { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageSP image = m_d->view->image(); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(this, SIGNAL(sigCanvasCacheUpdated()), SLOT(updateCanvasProjection())); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); emit imageChanged(image); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg; bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { qDebug()<<"Canvas: No proofing config found, generating one."; KisImageConfig cfg; m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; -#if QT_VERSION >= 0x07000 +#if QT_VERSION >= 0x050700 + if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof); if (softProof) { conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck); } } #else if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::SoftProofing; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing; } if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::GamutCheck; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck; } -#endif; +#endif m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { m_d->proofingConfig = KisImageConfig().defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) { QRect vRect = m_d->canvasWidget->updateCanvasProjection(info); if (!vRect.isEmpty()) { updateCanvasWidgetImpl(m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect()); } } // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { updateCanvasWidgetImpl(); } } void KisCanvas2::slotDoCanvasUpdate() { if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->updateSignalCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->updateSignalCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction } void KisCanvas2::slotTrySwitchShapeManager() { QPointer oldManager = m_d->currentlyActiveShapeManager; KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); if (newManager != oldManager) { m_d->currentlyActiveShapeManager = newManager; m_d->selectedShapesProxy.setShapeManager(newManager); } } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInCanvas()) return; KisImageSP image = this->image(); const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg; const int maxLod = cfg.numMipmapLevels(); int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); image->setDesiredLevelOfDetail(lod); } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); m_d->coordinatesConverter->setDocumentOffset(documentOffset); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotZoomChanged(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotZoomChanged(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInCanvas = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas()); notifyLevelOfDetailChange(); } KisConfig cfg; cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInCanvas; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/dialogs/kis_dlg_image_properties.cc b/libs/ui/dialogs/kis_dlg_image_properties.cc index 16f8eb7d93..668132567a 100644 --- a/libs/ui/dialogs/kis_dlg_image_properties.cc +++ b/libs/ui/dialogs/kis_dlg_image_properties.cc @@ -1,203 +1,204 @@ /* * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_image_properties.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 "widgets/kis_cmb_idlist.h" #include "widgets/squeezedcombobox.h" #include "kis_layer_utils.h" KisDlgImageProperties::KisDlgImageProperties(KisImageWSP image, QWidget *parent, const char *name) : KoDialog(parent) { setButtons(Ok | Cancel); setDefaultButton(Ok); setObjectName(name); setCaption(i18n("Image Properties")); m_page = new WdgImageProperties(this); m_image = image; setMainWidget(m_page); resize(m_page->sizeHint()); KisConfig cfg; m_page->lblWidthValue->setText(QString::number(image->width())); m_page->lblHeightValue->setText(QString::number(image->height())); m_page->lblResolutionValue->setText(QLocale().toString(image->xRes()*72, 2)); // XXX: separate values for x & y? //Set the canvas projection color: backgroundColor KoColor background = m_image->defaultProjectionColor(); background.setOpacity(1.0); m_page->bnBackgroundColor->setColor(background); m_page->sldBackgroundColor->setRange(0.0,1.0,2); m_page->sldBackgroundColor->setSingleStep(0.05); m_page->sldBackgroundColor->setValue(m_image->defaultProjectionColor().opacityF()); KisSignalCompressor *compressor = new KisSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_page->bnBackgroundColor, SIGNAL(changed(KoColor)), compressor, SLOT(start())); connect(m_page->sldBackgroundColor, SIGNAL(valueChanged(qreal)), compressor, SLOT(start())); connect(compressor, SIGNAL(timeout()), this, SLOT(setCurrentColor())); //Set the color space m_page->colorSpaceSelector->setCurrentColorSpace(image->colorSpace()); //set the proofing space m_proofingConfig = m_image->proofingConfiguration(); if (!m_proofingConfig) { m_page->chkSaveProofing->setChecked(false); m_proofingConfig = KisImageConfig().defaultProofingconfiguration(); } else { m_page->chkSaveProofing->setChecked(true); } m_page->proofSpaceSelector->setCurrentColorSpace(KoColorSpaceRegistry::instance()->colorSpace(m_proofingConfig->proofingModel, m_proofingConfig->proofingDepth, m_proofingConfig->proofingProfile)); m_page->cmbIntent->setCurrentIndex((int)m_proofingConfig->intent); m_page->ckbBlackPointComp->setChecked(m_proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->gamutAlarm->setColor(m_proofingConfig->warningColor); m_page->gamutAlarm->setToolTip(i18n("Set color used for warning")); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)m_proofingConfig->adaptationState*20); KisSignalCompressor *softProofConfigCompressor = new KisSignalCompressor(500, KisSignalCompressor::POSTPONE,this); connect(m_page->gamutAlarm, SIGNAL(changed(KoColor)), softProofConfigCompressor, SLOT(start())); connect(m_page->proofSpaceSelector, SIGNAL(colorSpaceChanged(const KoColorSpace*)), softProofConfigCompressor, SLOT(start())); connect(m_page->cmbIntent, SIGNAL(currentIndexChanged(int)), softProofConfigCompressor, SLOT(start())); connect(m_page->ckbBlackPointComp, SIGNAL(stateChanged(int)), softProofConfigCompressor, SLOT(start())); connect(m_page->sldAdaptationState, SIGNAL(valueChanged(int)), softProofConfigCompressor, SLOT(start())); connect(softProofConfigCompressor, SIGNAL(timeout()), this, SLOT(setProofingConfig())); //annotations vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); vKisAnnotationSP_it it = beginIt; while (it != endIt) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } m_page->cmbAnnotations->addItem((*it) -> type()); it++; } connect(m_page->cmbAnnotations, SIGNAL(activated(QString)), SLOT(setAnnotation(QString))); setAnnotation(m_page->cmbAnnotations->currentText()); } KisDlgImageProperties::~KisDlgImageProperties() { delete m_page; } const KoColorSpace * KisDlgImageProperties::colorSpace() { return m_page->colorSpaceSelector->currentColorSpace(); } void KisDlgImageProperties::setCurrentColor() { KoColor background = m_page->bnBackgroundColor->color(); background.setOpacity(m_page->sldBackgroundColor->value()); KisLayerUtils::changeImageDefaultProjectionColor(m_image, background); } void KisDlgImageProperties::setProofingConfig() { if (m_firstProofingConfigChange) { m_page->chkSaveProofing->setChecked(true); m_firstProofingConfigChange = false; } if (m_page->chkSaveProofing->isChecked()) { m_proofingConfig->conversionFlags = KoColorConversionTransformation::HighQuality; -#if QT_VERSION >= 0x07000 + +#if QT_VERSION >= 0x050700 m_proofingConfig->conversionFlags.setFlag(KoColorConversionTransformation::BlackpointCompensation, m_page->ckbBlackPointComp->isChecked()); #else - m_page->chkBlackPointComp->isChecked() ? + m_page->ckbBlackPointComp->isChecked() ? m_proofingConfig->conversionFlags |= KoColorConversionTransformation::BlackpointCompensation : m_proofingConfig->conversionFlags = m_proofingConfig->conversionFlags & ~KoColorConversionTransformation::BlackpointCompensation; #endif m_proofingConfig->intent = (KoColorConversionTransformation::Intent)m_page->cmbIntent->currentIndex(); m_proofingConfig->proofingProfile = m_page->proofSpaceSelector->currentColorSpace()->profile()->name(); m_proofingConfig->proofingModel = m_page->proofSpaceSelector->currentColorSpace()->colorModelId().id(); m_proofingConfig->proofingDepth = "U8";//default to this m_proofingConfig->warningColor = m_page->gamutAlarm->color(); m_proofingConfig->adaptationState = (double)m_page->sldAdaptationState->value()/20.0; m_image->setProofingConfiguration(m_proofingConfig); } else { - m_image->setProofingConfiguration(0); + m_image->setProofingConfiguration(KisProofingConfigurationSP()); } } void KisDlgImageProperties::setAnnotation(const QString &type) { KisAnnotationSP annotation = m_image->annotation(type); if (annotation) { m_page->lblDescription->clear(); m_page->txtAnnotation->clear(); m_page->lblDescription->setText(annotation->description()); m_page->txtAnnotation->appendPlainText(annotation->displayText()); } else { m_page->lblDescription->clear(); m_page->txtAnnotation->clear(); } } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 90f0693638..5ab1c1bfe5 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1271 +1,1291 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.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 "KoID.h" #include #include #include #include #include #include "kis_action_registry.h" #include "widgets/squeezedcombobox.h" #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #ifdef Q_OS_WIN # include #endif GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg; m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); + m_cmbKineticScrollingGesture->addItem(i18n("Disabled")); + m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); + m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); + m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); chkShowRootLayer->setChecked(cfg.showRootLayer()); int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); m_undoStackSize->setValue(cfg.undoStackLimit()); m_backupFileCheckBox->setChecked(cfg.backupFile()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_hideSplashScreen->setChecked(cfg.hideSplashScreen()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", true)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); m_chkCompressKra->setChecked(cfg.compressKra()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); + m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); + m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity()); + m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar()); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); } void GeneralTab::setDefault() { KisConfig cfg; m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); + m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); + m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity(true)); + m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } bool GeneralTab::hideSplashScreen() { return m_hideSplashScreen->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } +int GeneralTab::kineticScrollingGesture() +{ + return m_cmbKineticScrollingGesture->currentIndex(); +} + +int GeneralTab::kineticScrollingSensitivity() +{ + return m_kineticScrollingSensitivity->value(); +} + +bool GeneralTab::kineticScrollingScrollbar() +{ + return m_chkKineticScrollingScrollbar->isChecked(); +} + bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } bool GeneralTab::calculateAnimationCacheInBackground() { return m_chkCacheAnimatioInBackground->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; SqueezedComboBox *cmb = new SqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { if (useSystemProfile) { KisConfig cfg; QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA", "")); KisConfig cfg; KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { KisConfig cfg; m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #else m_page->grpTabletApi->setVisible(false); #endif } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { KisImageConfig cfg; return cfg.totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg; const int totalRAM = cfg.totalRAM(); lblTotalMemory->setText(i18n("%1 MiB", totalRAM)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 100); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); - connect(sliderFpsLimit, SIGNAL(valueChanged(int)), SLOT(slotFpsLimitChanged(int))); load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg; sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); - m_lastUsedFpsLimit = cfg.fpsLimit(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); - sliderFpsLimit->setValue(m_lastUsedFpsLimit); + + sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2; chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); } } void PerformanceTab::save() { KisImageConfig cfg; cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2; cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); } } void PerformanceTab::selectSwapDir() { KisImageConfig cfg; QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } -void PerformanceTab::slotFpsLimitChanged(int value) -{ - KisSignalsBlocker b(sliderFrameClonesLimit); - sliderFrameClonesLimit->setValue(qMax(m_lastUsedFpsLimit, value)); - m_lastUsedFpsLimit = value; -} - //--------------------------------------------------------------------------------------------------- #include "KoColor.h" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg; const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); const QString rendererAngleText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #ifdef Q_OS_WIN cmbRenderer->clear(); QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererAngle) { qtPreferredRendererText = rendererAngleText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbRenderer->setCurrentIndex(0); if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { cmbRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererAngle) { cmbRenderer->addItem(rendererAngleText, KisOpenGL::RendererAngle); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererAngle) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } #else lblRenderer->setEnabled(false); cmbRenderer->setEnabled(false); cmbRenderer->clear(); cmbRenderer->addItem(rendererOpenGLText); cmbRenderer->setCurrentIndex(0); #endif #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KoColor c; c.fromQColor(cfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg; cmbRenderer->setCurrentIndex(0); #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(KPageDialog::Tree); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText("Restore Defaults"); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); } KisDlgPreferences::~KisDlgPreferences() { } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg; cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen()); cfg.setCalculateAnimationCacheInBackground(dialog->m_general->calculateAnimationCacheInBackground()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); + cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); + cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); + cfg.setKineticScrollingScrollbar(dialog->m_general->kineticScrollingScrollbar()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage; cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); #ifdef Q_OS_WIN { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbRenderer->itemData( dialog->m_displaySettings->cmbRenderer->currentIndex()).toInt()); KisOpenGL::setNextUserOpenGLRendererConfig(renderer); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } #endif if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfg.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/dialogs/kis_dlg_preferences.h b/libs/ui/dialogs/kis_dlg_preferences.h index 30cfa3a6b5..e94669e510 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.h +++ b/libs/ui/dialogs/kis_dlg_preferences.h @@ -1,340 +1,341 @@ /* * preferencesdlg.h - part of KImageShop^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_DLG_PREFERENCES_H_ #define _KIS_DLG_PREFERENCES_H_ #include #include #include "kis_global.h" #include "widgets/squeezedcombobox.h" #include "ui_wdggeneralsettings.h" #include "ui_wdgdisplaysettings.h" #include "ui_wdgcolorsettings.h" #include "ui_wdgtabletsettings.h" #include "ui_wdgperformancesettings.h" #include "ui_wdgfullscreensettings.h" #include "KisShortcutsDialog.h" class KoID; class KisInputConfigurationPage; class KoConfigAuthorPage; /** * "General"-tab for preferences dialog */ class WdgGeneralSettings : public QWidget, public Ui::WdgGeneralSettings { Q_OBJECT public: WdgGeneralSettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); chkShowRootLayer->setVisible(false); } }; class GeneralTab : public WdgGeneralSettings { Q_OBJECT public: GeneralTab(QWidget *parent = 0, const char *name = 0); CursorStyle cursorStyle(); OutlineStyle outlineStyle(); bool showRootLayer(); int autoSaveInterval(); void setDefault(); int undoStackSize(); bool showOutlineWhilePainting(); bool hideSplashScreen(); int mdiMode(); int favoritePresets(); bool showCanvasMessages(); bool compressKra(); bool toolOptionsInDocker(); + int kineticScrollingGesture(); + int kineticScrollingSensitivity(); + bool kineticScrollingScrollbar(); bool switchSelectionCtrlAlt(); bool convertToImageColorspaceOnImport(); bool calculateAnimationCacheInBackground(); private Q_SLOTS: void getBackgroundImage(); void clearBackgroundImage(); }; /** * "Shortcuts" tab for preferences dialog */ class WdgShortcutSettings : public KisShortcutsDialog { Q_OBJECT public: WdgShortcutSettings(QWidget *parent) : KisShortcutsDialog(KisShortcutsEditor::AllActions, KisShortcutsEditor::LetterShortcutsAllowed, parent) { } }; class KisActionsSnapshot; class ShortcutSettingsTab : public QWidget { Q_OBJECT public: ShortcutSettingsTab(QWidget *parent = 0, const char *name = 0); ~ShortcutSettingsTab() override; public: void setDefault(); WdgShortcutSettings *m_page; QScopedPointer m_snapshot; public Q_SLOTS: void saveChanges(); void cancelChanges(); }; /** * "Color" tab for preferences dialog */ class WdgColorSettings : public QWidget, public Ui::WdgColorSettings { Q_OBJECT public: WdgColorSettings(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class ColorSettingsTab : public QWidget { Q_OBJECT public: ColorSettingsTab(QWidget *parent = 0, const char *name = 0); private Q_SLOTS: void refillMonitorProfiles(const KoID & s); void installProfile(); void toggleAllowMonitorProfileSelection(bool useSystemProfile); public: void setDefault(); WdgColorSettings *m_page; QButtonGroup m_pasteBehaviourGroup; QList m_monitorProfileLabels; QList m_monitorProfileWidgets; }; //======================= class WdgTabletSettings : public QWidget, public Ui::WdgTabletSettings { Q_OBJECT public: WdgTabletSettings(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class TabletSettingsTab : public QWidget { Q_OBJECT public: TabletSettingsTab(QWidget *parent = 0, const char *name = 0); public: void setDefault(); WdgTabletSettings *m_page; }; //======================= /** * "Performance"-tab for preferences dialog */ class SliderAndSpinBoxSync; class WdgPerformanceSettings : public QWidget, public Ui::WdgPerformanceSettings { Q_OBJECT public: WdgPerformanceSettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; class PerformanceTab : public WdgPerformanceSettings { Q_OBJECT public: PerformanceTab(QWidget *parent = 0, const char *name = 0); ~PerformanceTab() override; void load(bool requestDefault); void save(); private Q_SLOTS: void selectSwapDir(); void slotThreadsLimitChanged(int value); void slotFrameClonesLimitChanged(int value); - void slotFpsLimitChanged(int value); private: int realTilesRAM(); private: QVector m_syncs; int m_lastUsedThreadsLimit; int m_lastUsedClonesLimit; - int m_lastUsedFpsLimit; }; //======================= class WdgDisplaySettings : public QWidget, public Ui::WdgDisplaySettings { Q_OBJECT public: WdgDisplaySettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; /** * Display settings tab for preferences dialog */ class DisplaySettingsTab : public WdgDisplaySettings { Q_OBJECT public: DisplaySettingsTab(QWidget *parent = 0, const char *name = 0); public: void setDefault(); protected Q_SLOTS: void slotUseOpenGLToggled(bool isChecked); public: }; //======================= /** * Full screen settings tab for preferences dialog */ class WdgFullscreenSettingsBase : public QWidget, public Ui::WdgFullscreenSettings { Q_OBJECT public: WdgFullscreenSettingsBase(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class FullscreenSettingsTab : public WdgFullscreenSettingsBase { Q_OBJECT public: FullscreenSettingsTab(QWidget *parent); public: void setDefault(); }; //======================= /** * Preferences dialog of KImageShop^WKrayon^WKrita */ class KisDlgPreferences : public KPageDialog { Q_OBJECT public: static bool editPreferences(); protected: KisDlgPreferences(QWidget *parent = 0, const char *name = 0); ~KisDlgPreferences() override; protected: GeneralTab *m_general; ShortcutSettingsTab *m_shortcutSettings; ColorSettingsTab *m_colorSettings; PerformanceTab *m_performanceSettings; DisplaySettingsTab *m_displaySettings; TabletSettingsTab *m_tabletSettings; FullscreenSettingsTab *m_fullscreenSettings; KisInputConfigurationPage *m_inputConfiguration; KoConfigAuthorPage *m_authorPage; protected Q_SLOTS: void slotDefault(); }; #endif diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 677fb048c7..4f85c6c7f7 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,704 +1,762 @@ WdgGeneralSettings 0 0 759 468 0 0 552 295 Qt::LeftToRight 0 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 200 0 Show brush outline while painting Cursor Color: 48 25 Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Don't show contents when moving sub-windows: 0 0 Show on-canvas popup messages: Qt::Vertical 20 40 Enable Hi-DPI support: Allow only one instance of Krita: Tools 10 10 10 10 10 Tool Options Location (needs restart) In Doc&ker In Tool&bar true Switch Control/Alt Selection Modifiers Enable Touch Painting + + + + Kinetic Scrolling (needs restart) + + + + + + + + + + + Sensitivity (0-100): + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + 0 + + + 100 + + + 1 + + + 75 + + + + + + + + + Show Scrollbar + + + + + + Qt::Vertical 20 40 Qt::Horizontal 40 20 Miscellaneous 0 0 Qt::RightToLeft Autosave every: true 0 0 75 0 min 1 1440 5 15 Compress .kra files more (slows loading/saving) Create backup file On importing images as layers, convert to the image colorspace 0 0 Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 0 1000 5 30 0 0 Number of Palette Presets Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 10 30 Show root layer Hide splash screen on startup Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) Maximum brush size: 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) Qt::Vertical 504 13 Recalculate animation cache in background KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
diff --git a/libs/ui/forms/wdgpreseticonlibrary.ui b/libs/ui/forms/wdgpreseticonlibrary.ui new file mode 100644 index 0000000000..3fdcdfa58c --- /dev/null +++ b/libs/ui/forms/wdgpreseticonlibrary.ui @@ -0,0 +1,195 @@ + + + wdgpreseticonlibrary + + + + 0 + 0 + 536 + 505 + + + + + + + + 0 + 20 + + + + + + + + + 0 + 20 + + + + + + + + Color adjustment: + + + + + + + <html><head/><body><p>Choose the optional emblem icon that indicates extra information, such as the preset being a special effects brush, or just using tilt, or angled in some way.</p></body></html> + + + Emblem icon: + + + + + + + Upper left emblem indicating a special feature of the brush. + + + QAbstractItemView::NoEditTriggers + + + + 40 + 40 + + + + QListView::Adjust + + + + 42 + 42 + + + + QListView::IconMode + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 200 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Base tool image to base this preset on. + + + QAbstractItemView::NoEditTriggers + + + + 75 + 75 + + + + QListView::Static + + + QListView::Adjust + + + + 77 + 77 + + + + QListView::IconMode + + + + + + + + 0 + 20 + + + + + + + + Tool image: + + + + + + + Qt::Horizontal + + + + 40 + 6 + + + + + + + + + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+
+ + +
diff --git a/libs/ui/forms/wdgsavebrushpreset.ui b/libs/ui/forms/wdgsavebrushpreset.ui index fe91d5e344..91829277f7 100644 --- a/libs/ui/forms/wdgsavebrushpreset.ui +++ b/libs/ui/forms/wdgsavebrushpreset.ui @@ -1,226 +1,233 @@ WdgSaveBrushPreset 0 0 - 450 - 312 + 457 + 332 0 0 Save Brush Preset 13 Brush Name: 220 0 14 BrushName Qt::Horizontal 40 20 18 2 Paint in this area true 0 0 200 200 200 200 Qt::Vertical 20 40 6 Load Existing Thumbnail Load Scratchpad Thumbnail Load Image + + + + Load from Icon Library + + + Clear Thumbnail Qt::Vertical 20 40 Qt::Horizontal 40 20 Cancel Save KisScratchPad QWidget
kis_scratch_pad.h
1
diff --git a/libs/ui/input/kis_show_palette_action.cpp b/libs/ui/input/kis_show_palette_action.cpp index bfc85a8881..bbc421944b 100644 --- a/libs/ui/input/kis_show_palette_action.cpp +++ b/libs/ui/input/kis_show_palette_action.cpp @@ -1,104 +1,104 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_show_palette_action.h" #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_input_manager.h" KisShowPaletteAction::KisShowPaletteAction() : KisAbstractInputAction("Show Popup Palette"), m_requestedWithStylus(false) { setName(i18n("Show Popup Palette")); setDescription(i18n("The Show Popup Palette displays the popup palette.")); } KisShowPaletteAction::~KisShowPaletteAction() { } int KisShowPaletteAction::priority() const { return 1; } void KisShowPaletteAction::begin(int, QEvent *event) { m_menu = inputManager()->toolProxy()->popupActionsMenu(); if (m_menu) { - m_requestedWithStylus = event->type() == QEvent::TabletPress; + m_requestedWithStylus = event && event->type() == QEvent::TabletPress; /** * Opening a menu changes the focus of the windows, so we should not open it * inside the filtering loop. Just raise it using the timer. */ QTimer::singleShot(0, this, SLOT(slotShowMenu())); } else { QPoint pos = eventPos(event); if (pos.isNull()) { pos = inputManager()->canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); } inputManager()->canvas()->slotShowPopupPalette(pos); } } struct SinglePressEventEater : public QObject { bool eventFilter(QObject *, QEvent *event) override { if (hungry && event->type() == QEvent::MouseButtonPress) { hungry = false; return true; } return false; } private: bool hungry = true; }; void KisShowPaletteAction::slotShowMenu() { if (m_menu) { QPoint stylusOffset; QScopedPointer eater; if (m_requestedWithStylus) { eater.reset(new SinglePressEventEater()); m_menu->installEventFilter(eater.data()); stylusOffset += QPoint(10,10); } m_menu->exec(QCursor::pos() + stylusOffset); m_menu.clear(); } } diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 3231fd3cd4..97ec6bb6f7 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,1943 +1,1973 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include KisConfig::KisConfig() : m_cfg( KSharedConfig::openConfig()->group("")) { } KisConfig::~KisConfig() { if (qApp->thread() != QThread::currentThread()) { //dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Skipping..."; return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col)); } void KisConfig::setMDIBackgroundColor(const QColor &v) const { m_cfg.writeEntry("mdiBackgroundColor", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg; QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString cs = canvasState(); #ifdef Q_OS_WIN return (m_cfg.readEntry("useOpenGLWindows", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #else return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #endif } void KisConfig::setUseOpenGL(bool useOpenGL) const { #ifdef Q_OS_WIN m_cfg.writeEntry("useOpenGLWindows", useOpenGL); #else m_cfg.writeEntry("useOpenGL", useOpenGL); #endif } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 8.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const { QColor def(255, 0, 0, 220); return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def)); } void KisConfig::setSelectionOverlayMaskColor(const QColor &color) { m_cfg.writeEntry("selectionOverlayMaskColor", color); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::hideSplashScreen(bool defaultValue) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); return (defaultValue ? true : cfg.readEntry("HideSplashAfterStartup", true)); } void KisConfig::setHideSplashScreen(bool hideSplashScreen) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); cfg.writeEntry("HideSplashAfterStartup", hideSplashScreen); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) const { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { m_cfg.writeEntry("useWin8PointerInput", value); } #else Q_UNUSED(value) #endif } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("presetIconSize", 30)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockerTitleBars(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockerTitleBars", true)); } void KisConfig::setShowDockerTitleBars(const bool value) const { m_cfg.writeEntry("showDockerTitleBars", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } QString KisConfig::ocioConfigurationPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString())); } void KisConfig::setOcioConfigurationPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path); } QString KisConfig::ocioLutPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString())); } void KisConfig::setOcioLutPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", QString())); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::paletteDockerPaletteViewSectionSize(bool defaultValue) const { return (defaultValue ? 12 : m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12)); } void KisConfig::setPaletteDockerPaletteViewSectionSize(int value) const { m_cfg.writeEntry("paletteDockerPaletteViewSectionSize", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } +int KisConfig::kineticScrollingGesture(bool defaultValue) const +{ + return (defaultValue ? 0 : m_cfg.readEntry("KineticScrollingGesture", 0)); +} + +void KisConfig::setKineticScrollingGesture(int gesture) +{ + m_cfg.writeEntry("KineticScrollingGesture", gesture); +} + +int KisConfig::kineticScrollingSensitivity(bool defaultValue) const +{ + return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); +} + +void KisConfig::setKineticScrollingSensitivity(int sensitivity) +{ + m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); +} + +bool KisConfig::kineticScrollingScrollbar(bool defaultValue) const +{ + return (defaultValue ? true : m_cfg.readEntry("KineticScrollingScrollbar", true)); +} + +void KisConfig::setKineticScrollingScrollbar(bool scrollbar) +{ + m_cfg.writeEntry("KineticScrollingScrollbar", scrollbar); +} + const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } QString KisConfig::customFFMpegPath(bool defaultValue) const { return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString()); } void KisConfig::setCustomFFMpegPath(const QString &value) const { m_cfg.writeEntry("ffmpegExecutablePath", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h index 557c2119f3..3fcea68632 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,574 +1,583 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CONFIG_H_ #define KIS_CONFIG_H_ #include #include #include #include #include #include #include "kis_global.h" #include "kis_properties_configuration.h" #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class KRITAUI_EXPORT KisConfig { public: KisConfig(); ~KisConfig(); bool disableTouchOnCanvas(bool defaultValue = false) const; void setDisableTouchOnCanvas(bool value) const; bool useProjections(bool defaultValue = false) const; void setUseProjections(bool useProj) const; bool undoEnabled(bool defaultValue = false) const; void setUndoEnabled(bool undo) const; int undoStackLimit(bool defaultValue = false) const; void setUndoStackLimit(int limit) const; bool useCumulativeUndoRedo(bool defaultValue = false) const; void setCumulativeUndoRedo(bool value); double stackT1(bool defaultValue = false) const; void setStackT1(int T1); double stackT2(bool defaultValue = false) const; void setStackT2(int T2); int stackN(bool defaultValue = false) const; void setStackN(int N); qint32 defImageWidth(bool defaultValue = false) const; void defImageWidth(qint32 width) const; qint32 defImageHeight(bool defaultValue = false) const; void defImageHeight(qint32 height) const; qreal defImageResolution(bool defaultValue = false) const; void defImageResolution(qreal res) const; /** * @return the id of the default color model used for creating new images. */ QString defColorModel(bool defaultValue = false) const; /** * set the id of the default color model used for creating new images. */ void defColorModel(const QString & model) const; /** * @return the id of the default color depth used for creating new images. */ QString defaultColorDepth(bool defaultValue = false) const; /** * set the id of the default color depth used for creating new images. */ void setDefaultColorDepth(const QString & depth) const; /** * @return the id of the default color profile used for creating new images. */ QString defColorProfile(bool defaultValue = false) const; /** * set the id of the default color profile used for creating new images. */ void defColorProfile(const QString & depth) const; CursorStyle newCursorStyle(bool defaultValue = false) const; void setNewCursorStyle(CursorStyle style); QColor getCursorMainColor(bool defaultValue = false) const; void setCursorMainColor(const QColor& v) const; OutlineStyle newOutlineStyle(bool defaultValue = false) const; void setNewOutlineStyle(OutlineStyle style); QRect colorPreviewRect() const; void setColorPreviewRect(const QRect &rect); /// get the profile the user has selected for the given screen QString monitorProfile(int screen) const; void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const; QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const; void setMonitorForScreen(int screen, const QString& monitor); /// Get the actual profile to be used for the given screen, which is /// either the screen profile set by the color management system or /// the custom monitor profile set by the user, depending on the configuration const KoColorProfile *displayProfile(int screen) const; QString workingColorSpace(bool defaultValue = false) const; void setWorkingColorSpace(const QString & workingColorSpace) const; QString importProfile(bool defaultValue = false) const; void setImportProfile(const QString & importProfile) const; QString printerColorSpace(bool defaultValue = false) const; void setPrinterColorSpace(const QString & printerColorSpace) const; QString printerProfile(bool defaultValue = false) const; void setPrinterProfile(const QString & printerProfile) const; bool useBlackPointCompensation(bool defaultValue = false) const; void setUseBlackPointCompensation(bool useBlackPointCompensation) const; bool allowLCMSOptimization(bool defaultValue = false) const; void setAllowLCMSOptimization(bool allowLCMSOptimization); void writeKoColor(const QString& name, const KoColor& color) const; KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const; bool showRulers(bool defaultValue = false) const; void setShowRulers(bool rulers) const; bool forceShowSaveMessages(bool defaultValue = true) const; void setForceShowSaveMessages(bool value) const; bool forceShowAutosaveMessages(bool defaultValue = true) const; void setForceShowAutosaveMessages(bool ShowAutosaveMessages) const; bool rulersTrackMouse(bool defaultValue = false) const; void setRulersTrackMouse(bool value) const; qint32 pasteBehaviour(bool defaultValue = false) const; void setPasteBehaviour(qint32 behaviour) const; qint32 monitorRenderIntent(bool defaultValue = false) const; void setRenderIntent(qint32 monitorRenderIntent) const; bool useOpenGL(bool defaultValue = false) const; void setUseOpenGL(bool useOpenGL) const; int openGLFilteringMode(bool defaultValue = false) const; void setOpenGLFilteringMode(int filteringMode); bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); bool showAdvancedOpenGLSettings(bool defaultValue = false) const; bool forceOpenGLFenceWorkaround(bool defaultValue = false) const; int numMipmapLevels(bool defaultValue = false) const; int openGLTextureSize(bool defaultValue = false) const; int textureOverlapBorder() const; quint32 getGridMainStyle(bool defaultValue = false) const; void setGridMainStyle(quint32 v) const; quint32 getGridSubdivisionStyle(bool defaultValue = false) const; void setGridSubdivisionStyle(quint32 v) const; QColor getGridMainColor(bool defaultValue = false) const; void setGridMainColor(const QColor & v) const; QColor getGridSubdivisionColor(bool defaultValue = false) const; void setGridSubdivisionColor(const QColor & v) const; QColor getPixelGridColor(bool defaultValue = false) const; void setPixelGridColor(const QColor & v) const; qreal getPixelGridDrawingThreshold(bool defaultValue = false) const; void setPixelGridDrawingThreshold(qreal v) const; bool pixelGridEnabled(bool defaultValue = false) const; void enablePixelGrid(bool v) const; quint32 guidesLineStyle(bool defaultValue = false) const; void setGuidesLineStyle(quint32 v) const; QColor guidesColor(bool defaultValue = false) const; void setGuidesColor(const QColor & v) const; void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const; void saveSnapConfig(const KisSnapConfig &config); qint32 checkSize(bool defaultValue = false) const; void setCheckSize(qint32 checkSize) const; bool scrollCheckers(bool defaultValue = false) const; void setScrollingCheckers(bool scollCheckers) const; QColor checkersColor1(bool defaultValue = false) const; void setCheckersColor1(const QColor & v) const; QColor checkersColor2(bool defaultValue = false) const; void setCheckersColor2(const QColor & v) const; QColor canvasBorderColor(bool defaultValue = false) const; void setCanvasBorderColor(const QColor &color) const; bool hideScrollbars(bool defaultValue = false) const; void setHideScrollbars(bool value) const; bool antialiasCurves(bool defaultValue = false) const; void setAntialiasCurves(bool v) const; QColor selectionOverlayMaskColor(bool defaultValue = false) const; void setSelectionOverlayMaskColor(const QColor &color); bool antialiasSelectionOutline(bool defaultValue = false) const; void setAntialiasSelectionOutline(bool v) const; bool showRootLayer(bool defaultValue = false) const; void setShowRootLayer(bool showRootLayer) const; bool showGlobalSelection(bool defaultValue = false) const; void setShowGlobalSelection(bool showGlobalSelection) const; bool showOutlineWhilePainting(bool defaultValue = false) const; void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const; bool hideSplashScreen(bool defaultValue = false) const; void setHideSplashScreen(bool hideSplashScreen) const; qreal outlineSizeMinimum(bool defaultValue = false) const; void setOutlineSizeMinimum(qreal outlineSizeMinimum) const; qreal selectionViewSizeMinimum(bool defaultValue = false) const; void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const; int autoSaveInterval(bool defaultValue = false) const; void setAutoSaveInterval(int seconds) const; bool backupFile(bool defaultValue = false) const; void setBackupFile(bool backupFile) const; bool showFilterGallery(bool defaultValue = false) const; void setShowFilterGallery(bool showFilterGallery) const; bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const; void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const; // OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED QString canvasState(bool defaultValue = false) const; void setCanvasState(const QString& state) const; bool toolOptionsPopupDetached(bool defaultValue = false) const; void setToolOptionsPopupDetached(bool detached) const; bool paintopPopupDetached(bool defaultValue = false) const; void setPaintopPopupDetached(bool detached) const; QString pressureTabletCurve(bool defaultValue = false) const; void setPressureTabletCurve(const QString& curveString) const; bool useWin8PointerInput(bool defaultValue = false) const; void setUseWin8PointerInput(bool value) const; qreal vastScrolling(bool defaultValue = false) const; void setVastScrolling(const qreal factor) const; int presetChooserViewMode(bool defaultValue = false) const; void setPresetChooserViewMode(const int mode) const; int presetIconSize(bool defaultValue = false) const; void setPresetIconSize(const int value) const; bool firstRun(bool defaultValue = false) const; void setFirstRun(const bool firstRun) const; bool clicklessSpacePan(bool defaultValue = false) const; void setClicklessSpacePan(const bool toggle) const; int horizontalSplitLines(bool defaultValue = false) const; void setHorizontalSplitLines(const int numberLines) const; int verticalSplitLines(bool defaultValue = false) const; void setVerticalSplitLines(const int numberLines) const; bool hideDockersFullscreen(bool defaultValue = false) const; void setHideDockersFullscreen(const bool value) const; bool showDockerTitleBars(bool defaultValue = false) const; void setShowDockerTitleBars(const bool value) const; bool showDockers(bool defaultValue = false) const; void setShowDockers(const bool value) const; bool showStatusBar(bool defaultValue = false) const; void setShowStatusBar(const bool value) const; bool hideMenuFullscreen(bool defaultValue = false) const; void setHideMenuFullscreen(const bool value) const; bool hideScrollbarsFullscreen(bool defaultValue = false) const; void setHideScrollbarsFullscreen(const bool value) const; bool hideStatusbarFullscreen(bool defaultValue = false) const; void setHideStatusbarFullscreen(const bool value) const; bool hideTitlebarFullscreen(bool defaultValue = false) const; void setHideTitlebarFullscreen(const bool value) const; bool hideToolbarFullscreen(bool defaultValue = false) const; void setHideToolbarFullscreen(const bool value) const; bool fullscreenMode(bool defaultValue = false) const; void setFullscreenMode(const bool value) const; QStringList favoriteCompositeOps(bool defaultValue = false) const; void setFavoriteCompositeOps(const QStringList& compositeOps) const; QString exportConfiguration(const QString &filterId, bool defaultValue = false) const; void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; QString importConfiguration(const QString &filterId, bool defaultValue = false) const; void setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; bool useOcio(bool defaultValue = false) const; void setUseOcio(bool useOCIO) const; int favoritePresets(bool defaultValue = false) const; void setFavoritePresets(const int value); bool levelOfDetailEnabled(bool defaultValue = false) const; void setLevelOfDetailEnabled(bool value); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) const; QString ocioConfigurationPath(bool defaultValue = false) const; void setOcioConfigurationPath(const QString &path) const; QString ocioLutPath(bool defaultValue = false) const; void setOcioLutPath(const QString &path) const; int ocioLutEdgeSize(bool defaultValue = false) const; void setOcioLutEdgeSize(int value); bool ocioLockColorVisualRepresentation(bool defaultValue = false) const; void setOcioLockColorVisualRepresentation(bool value); bool useSystemMonitorProfile(bool defaultValue = false) const; void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const; QString defaultPalette(bool defaultValue = false) const; void setDefaultPalette(const QString& name) const; QString toolbarSlider(int sliderNumber, bool defaultValue = false) const; void setToolbarSlider(int sliderNumber, const QString &slider); bool sliderLabels(bool defaultValue = false) const; void setSliderLabels(bool enabled); QString currentInputProfile(bool defaultValue = false) const; void setCurrentInputProfile(const QString& name); bool presetStripVisible(bool defaultValue = false) const; void setPresetStripVisible(bool visible); bool scratchpadVisible(bool defaultValue = false) const; void setScratchpadVisible(bool visible); bool showSingleChannelAsColor(bool defaultValue = false) const; void setShowSingleChannelAsColor(bool asColor); bool hidePopups(bool defaultValue = false) const; void setHidePopups(bool hidepopups); int numDefaultLayers(bool defaultValue = false) const; void setNumDefaultLayers(int num); quint8 defaultBackgroundOpacity(bool defaultValue = false) const; void setDefaultBackgroundOpacity(quint8 value); QColor defaultBackgroundColor(bool defaultValue = false) const; void setDefaultBackgroundColor(QColor value); enum BackgroundStyle { LAYER = 0, PROJECTION = 1 }; BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const; void setDefaultBackgroundStyle(BackgroundStyle value); int lineSmoothingType(bool defaultValue = false) const; void setLineSmoothingType(int value); qreal lineSmoothingDistance(bool defaultValue = false) const; void setLineSmoothingDistance(qreal value); qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const; void setLineSmoothingTailAggressiveness(qreal value); bool lineSmoothingSmoothPressure(bool defaultValue = false) const; void setLineSmoothingSmoothPressure(bool value); bool lineSmoothingScalableDistance(bool defaultValue = false) const; void setLineSmoothingScalableDistance(bool value); qreal lineSmoothingDelayDistance(bool defaultValue = false) const; void setLineSmoothingDelayDistance(qreal value); bool lineSmoothingUseDelayDistance(bool defaultValue = false) const; void setLineSmoothingUseDelayDistance(bool value); bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const; void setLineSmoothingFinishStabilizedCurve(bool value); bool lineSmoothingStabilizeSensors(bool defaultValue = false) const; void setLineSmoothingStabilizeSensors(bool value); int paletteDockerPaletteViewSectionSize(bool defaultValue = false) const; void setPaletteDockerPaletteViewSectionSize(int value) const; int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); bool trackTabletEventLatency(bool defaultValue = false) const; void setTrackTabletEventLatency(bool value); bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); bool shouldEatDriverShortcuts(bool defaultValue = false) const; bool testingCompressBrushEvents(bool defaultValue = false) const; void setTestingCompressBrushEvents(bool value); const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const; void setCustomColorSelectorColorSpace(const KoColorSpace *cs); bool useDirtyPresets(bool defaultValue = false) const; void setUseDirtyPresets(bool value); bool useEraserBrushSize(bool defaultValue = false) const; void setUseEraserBrushSize(bool value); bool useEraserBrushOpacity(bool defaultValue = false) const; void setUseEraserBrushOpacity(bool value); QColor getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QColor & v) const; QString getMDIBackgroundImage(bool defaultValue = false) const; void setMDIBackgroundImage(const QString & fileName) const; int workaroundX11SmoothPressureSteps(bool defaultValue = false) const; bool showCanvasMessages(bool defaultValue = false) const; void setShowCanvasMessages(bool show); bool compressKra(bool defaultValue = false) const; void setCompressKra(bool compress); bool toolOptionsInDocker(bool defaultValue = false) const; void setToolOptionsInDocker(bool inDocker); + int kineticScrollingGesture(bool defaultValue = false) const; + void setKineticScrollingGesture(int kineticScroll); + + int kineticScrollingSensitivity(bool defaultValue = false) const; + void setKineticScrollingSensitivity(int sensitivity); + + bool kineticScrollingScrollbar(bool defaultValue = false) const; + void setKineticScrollingScrollbar(bool scrollbar); + void setEnableOpenGLFramerateLogging(bool value) const; bool enableOpenGLFramerateLogging(bool defaultValue = false) const; void setEnableBrushSpeedLogging(bool value) const; bool enableBrushSpeedLogging(bool defaultValue = false) const; void setEnableAmdVectorizationWorkaround(bool value); bool enableAmdVectorizationWorkaround(bool defaultValue = false) const; bool animationDropFrames(bool defaultValue = false) const; void setAnimationDropFrames(bool value); int scrubbingUpdatesDelay(bool defaultValue = false) const; void setScrubbingUpdatesDelay(int value); int scrubbingAudioUpdatesDelay(bool defaultValue = false) const; void setScrubbingAudioUpdatesDelay(int value); int audioOffsetTolerance(bool defaultValue = false) const; void setAudioOffsetTolerance(int value); bool switchSelectionCtrlAlt(bool defaultValue = false) const; void setSwitchSelectionCtrlAlt(bool value); bool convertToImageColorspaceOnImport(bool defaultValue = false) const; void setConvertToImageColorspaceOnImport(bool value); int stabilizerSampleSize(bool defaultValue = false) const; void setStabilizerSampleSize(int value); bool stabilizerDelayedPaint(bool defaultValue = false) const; void setStabilizerDelayedPaint(bool value); QString customFFMpegPath(bool defaultValue = false) const; void setCustomFFMpegPath(const QString &value) const; bool showBrushHud(bool defaultValue = false) const; void setShowBrushHud(bool value); QString brushHudSetting(bool defaultValue = false) const; void setBrushHudSetting(const QString &value) const; bool calculateAnimationCacheInBackground(bool defaultValue = false) const; void setCalculateAnimationCacheInBackground(bool value); template void writeEntry(const QString& name, const T& value) { m_cfg.writeEntry(name, value); } template void writeList(const QString& name, const QList& value) { m_cfg.writeEntry(name, value); } template T readEntry(const QString& name, const T& defaultValue=T()) { return m_cfg.readEntry(name, defaultValue); } template QList readList(const QString& name, const QList& defaultValue=QList()) { return m_cfg.readEntry(name, defaultValue); } /// get the profile the color managment system has stored for the given screen static const KoColorProfile* getScreenProfile(int screen); private: KisConfig(const KisConfig&); KisConfig& operator=(const KisConfig&) const; private: mutable KConfigGroup m_cfg; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 6174bfecfe..2e4e6b0e40 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,904 +1,904 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_OS_OSX #include #endif #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; bool displayShaderCompiledWithDisplayFilterSupport{false}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; QOpenGLBuffer lineBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; #ifndef Q_OS_OSX QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; + QColor cursorColor; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg; cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_OSX setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, true); setAttribute(Qt::WA_DontCreateNativeAncestors, true); setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); #ifndef Q_OS_OSX if (!KisOpenGL::hasOpenGLES()) { d->glFn201 = context()->versionFunctions(); if (!d->glFn201) { warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; } } else { d->glFn201 = nullptr; } #endif KisConfig cfg; d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineBuffer.create(); d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); delete d->checkerShader; delete d->solidColorShader; d->checkerShader = 0; d->solidColorShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } initializeDisplayShader(); } void KisOpenGLCanvas2::initializeDisplayShader() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; delete d->displayShader; d->displayShader = 0; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg; qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), QString(i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.")).arg(context), QMessageBox::Close); cfg.setUseOpenGL(false); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int width, int height) { coordinatesConverter()->setCanvasWidgetSize(QSize(width, height)); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); if (!KisOpenGL::hasOpenGLES()) { glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); #ifndef Q_OS_OSX if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } #else glLogicOp(GL_XOR); #endif } else { glEnable(GL_BLEND); glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE, GL_ONE); } - KisConfig cfg; - QColor cursorColor = cfg.getCursorMainColor(); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), - QVector4D(cursorColor.redF(), cursorColor.greenF(), cursorColor.blueF(), 1.0f)); + QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int i = 0; i < subPathPolygons.size(); i++) { const QPolygonF& polygon = subPathPolygons.at(i); QVector vertices; vertices.resize(polygon.count()); for (int j = 0; j < polygon.count(); j++) { QPointF p = polygon.at(j); vertices[j].setX(p.x()); vertices[j].setY(p.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); } glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } if (!KisOpenGL::hasOpenGLES()) { glDisable(GL_COLOR_LOGIC_OP); } else { glDisable(GL_BLEND); } d->solidColorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(this->rect()); converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); glDisable(GL_BLEND); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); qreal scaleX, scaleY; converter->imageScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect(tile->tileRectInTexturePixels()); QRectF modelRect(tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y())); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } int currentLodPlane = tile->currentLodPlane(); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } glActiveTexture(GL_TEXTURE0); tile->bindToActiveTexture(); if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisable(GL_BLEND); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg; d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); + d->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); glClearColor(widgetBackgroundColor.redF(), widgetBackgroundColor.greenF(), widgetBackgroundColor.blueF(), 1.0); glClear(GL_COLOR_BUFFER_BIT); if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); d->canvasInitialized = false; // TODO: check if actually needed? initializeDisplayShader(); d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayProfile(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image()); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info); } #ifdef Q_OS_OSX /** * There is a bug on OSX: if we issue frame redraw before the tiles finished * uploading, the tiles will become corrupted. Depending on the GPU/driver * version either the tile itself, or its mipmaps will become totally * transparent. */ glFinish(); #endif return QRect(); // FIXME: Implement dirty rect for OpenGL } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/widgets/kis_custom_image_widget.cc b/libs/ui/widgets/kis_custom_image_widget.cc index ee5fd39170..a6e9417718 100644 --- a/libs/ui/widgets/kis_custom_image_widget.cc +++ b/libs/ui/widgets/kis_custom_image_widget.cc @@ -1,503 +1,503 @@ /* This file is part of the Calligra project * Copyright (C) 2005 Thomas Zander * Copyright (C) 2005 C. Boemann * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "widgets/kis_custom_image_widget.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 "kis_config.h" #include "KisPart.h" #include "kis_clipboard.h" #include "KisDocument.h" #include "widgets/kis_cmb_idlist.h" #include "widgets/squeezedcombobox.h" KisCustomImageWidget::KisCustomImageWidget(QWidget* parent, qint32 defWidth, qint32 defHeight, double resolution, const QString& defColorModel, const QString& defColorDepth, const QString& defColorProfile, const QString& imageName) : WdgNewImage(parent) { setObjectName("KisCustomImageWidget"); m_openPane = qobject_cast(parent); Q_ASSERT(m_openPane); txtName->setText(imageName); m_widthUnit = KoUnit(KoUnit::Pixel, resolution); doubleWidth->setValue(defWidth); doubleWidth->setDecimals(0); m_width = m_widthUnit.fromUserValue(defWidth); cmbWidthUnit->addItems(KoUnit::listOfUnitNameForUi(KoUnit::ListAll)); cmbWidthUnit->setCurrentIndex(m_widthUnit.indexInListForUi(KoUnit::ListAll)); m_heightUnit = KoUnit(KoUnit::Pixel, resolution); doubleHeight->setValue(defHeight); doubleHeight->setDecimals(0); m_height = m_heightUnit.fromUserValue(defHeight); cmbHeightUnit->addItems(KoUnit::listOfUnitNameForUi(KoUnit::ListAll)); cmbHeightUnit->setCurrentIndex(m_heightUnit.indexInListForUi(KoUnit::ListAll)); doubleResolution->setValue(72.0 * resolution); doubleResolution->setDecimals(0); imageGroupSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); grpClipboard->hide(); sliderOpacity->setRange(0, 100, 0); sliderOpacity->setValue(100); sliderOpacity->setSuffix("%"); connect(cmbPredefined, SIGNAL(activated(int)), SLOT(predefinedClicked(int))); connect(doubleResolution, SIGNAL(valueChanged(double)), this, SLOT(resolutionChanged(double))); connect(cmbWidthUnit, SIGNAL(activated(int)), this, SLOT(widthUnitChanged(int))); connect(doubleWidth, SIGNAL(valueChanged(double)), this, SLOT(widthChanged(double))); connect(cmbHeightUnit, SIGNAL(activated(int)), this, SLOT(heightUnitChanged(int))); connect(doubleHeight, SIGNAL(valueChanged(double)), this, SLOT(heightChanged(double))); connect(createButton, SIGNAL(clicked()), this, SLOT(createImage())); createButton->setDefault(true); bnPortrait->setIcon(KisIconUtils::loadIcon("portrait")); connect(bnPortrait, SIGNAL(clicked()), SLOT(setPortrait())); connect(bnLandscape, SIGNAL(clicked()), SLOT(setLandscape())); bnLandscape->setIcon(KisIconUtils::loadIcon("landscape")); connect(doubleWidth, SIGNAL(valueChanged(double)), this, SLOT(switchPortraitLandscape())); connect(doubleHeight, SIGNAL(valueChanged(double)), this, SLOT(switchPortraitLandscape())); connect(bnSaveAsPredefined, SIGNAL(clicked()), this, SLOT(saveAsPredefined())); colorSpaceSelector->setCurrentColorModel(KoID(defColorModel)); colorSpaceSelector->setCurrentColorDepth(KoID(defColorDepth)); colorSpaceSelector->setCurrentProfile(defColorProfile); connect(colorSpaceSelector, SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(changeDocumentInfoLabel())); //connect(chkFromClipboard,SIGNAL(stateChanged(int)),this,SLOT(clipboardDataChanged())); connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged())); connect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, SLOT(clipboardDataChanged())); connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), this, SLOT(clipboardDataChanged())); connect(colorSpaceSelector, SIGNAL(selectionChanged(bool)), createButton, SLOT(setEnabled(bool))); KisConfig cfg; intNumLayers->setValue(cfg.numDefaultLayers()); KoColor bcol(KoColorSpaceRegistry::instance()->rgb8()); bcol.fromQColor(cfg.defaultBackgroundColor()); cmbColor->setColor(bcol); setBackgroundOpacity(cfg.defaultBackgroundOpacity()); KisConfig::BackgroundStyle bgStyle = cfg.defaultBackgroundStyle(); if (bgStyle == KisConfig::LAYER) { radioBackgroundAsLayer->setChecked(true); } else { radioBackgroundAsProjection->setChecked(true); } fillPredefined(); switchPortraitLandscape(); // this makes the portrait and landscape buttons more // obvious what is selected by changing the higlight color QPalette p = QApplication::palette(); QPalette palette_highlight(p ); QColor c = p.color(QPalette::Highlight); palette_highlight.setColor(QPalette::Button, c); bnLandscape->setPalette(palette_highlight); bnPortrait->setPalette(palette_highlight); changeDocumentInfoLabel(); } void KisCustomImageWidget::showEvent(QShowEvent *) { fillPredefined(); this->createButton->setFocus(); this->createButton->setEnabled(true); } KisCustomImageWidget::~KisCustomImageWidget() { m_predefined.clear(); } void KisCustomImageWidget::resolutionChanged(double res) { if (m_widthUnit.type() == KoUnit::Pixel) { m_widthUnit.setFactor(res / 72.0); m_width = m_widthUnit.fromUserValue(doubleWidth->value()); } if (m_heightUnit.type() == KoUnit::Pixel) { m_heightUnit.setFactor(res / 72.0); m_height = m_heightUnit.fromUserValue(doubleHeight->value()); } changeDocumentInfoLabel(); } void KisCustomImageWidget::widthUnitChanged(int index) { doubleWidth->blockSignals(true); m_widthUnit = KoUnit::fromListForUi(index, KoUnit::ListAll); if (m_widthUnit.type() == KoUnit::Pixel) { doubleWidth->setDecimals(0); m_widthUnit.setFactor(doubleResolution->value() / 72.0); } else { doubleWidth->setDecimals(2); } doubleWidth->setValue(KoUnit::ptToUnit(m_width, m_widthUnit)); doubleWidth->blockSignals(false); changeDocumentInfoLabel(); } void KisCustomImageWidget::widthChanged(double value) { m_width = m_widthUnit.fromUserValue(value); changeDocumentInfoLabel(); } void KisCustomImageWidget::heightUnitChanged(int index) { doubleHeight->blockSignals(true); m_heightUnit = KoUnit::fromListForUi(index, KoUnit::ListAll); if (m_heightUnit.type() == KoUnit::Pixel) { doubleHeight->setDecimals(0); m_heightUnit.setFactor(doubleResolution->value() / 72.0); } else { doubleHeight->setDecimals(2); } doubleHeight->setValue(KoUnit::ptToUnit(m_height, m_heightUnit)); doubleHeight->blockSignals(false); changeDocumentInfoLabel(); } void KisCustomImageWidget::heightChanged(double value) { m_height = m_heightUnit.fromUserValue(value); changeDocumentInfoLabel(); } void KisCustomImageWidget::createImage() { createButton->setEnabled(false); KisDocument *doc = createNewImage(); if (doc) { doc->setModified(false); emit m_openPane->documentSelected(doc); } } KisDocument* KisCustomImageWidget::createNewImage() { const KoColorSpace * cs = colorSpaceSelector->currentColorSpace(); if (cs->colorModelId() == RGBAColorModelID && cs->colorDepthId() == Integer8BitsColorDepthID) { const KoColorProfile *profile = cs->profile(); if (profile->name().contains("linear") || profile->name().contains("scRGB") || profile->info().contains("linear") || profile->info().contains("scRGB")) { int result = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Linear gamma RGB color spaces are not supposed to be used " "in 8-bit integer modes. It is suggested to use 16-bit integer " "or any floating point colorspace for linear profiles.\n\n" "Press \"Continue\" to create a 8-bit integer linear RGB color space " "or \"Cancel\" to return to the settings dialog."), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel); if (result == QMessageBox::Cancel) { dbgKrita << "Model RGB8" << "NOT SUPPORTED"; dbgKrita << ppVar(cs->name()); dbgKrita << ppVar(cs->profile()->name()); dbgKrita << ppVar(cs->profile()->info()); return 0; } } } KisDocument *doc = static_cast(KisPart::instance()->createDocument()); qint32 width, height; double resolution; resolution = doubleResolution->value() / 72.0; // internal resolution is in pixels per pt width = static_cast(0.5 + KoUnit::ptToUnit(m_width, KoUnit(KoUnit::Pixel, resolution))); height = static_cast(0.5 + KoUnit::ptToUnit(m_height, KoUnit(KoUnit::Pixel, resolution))); QColor qc = cmbColor->color().toQColor(); qc.setAlpha(backgroundOpacity()); KoColor bgColor(qc, cs); bool backgroundAsLayer = radioBackgroundAsLayer->isChecked(); doc->newImage(txtName->text(), width, height, cs, bgColor, backgroundAsLayer, intNumLayers->value(), txtDescription->toPlainText(), resolution); KisConfig cfg; cfg.setNumDefaultLayers(intNumLayers->value()); cfg.setDefaultBackgroundOpacity(backgroundOpacity()); cfg.setDefaultBackgroundColor(cmbColor->color().toQColor()); cfg.setDefaultBackgroundStyle(backgroundAsLayer ? KisConfig::LAYER : KisConfig::PROJECTION); return doc; } void KisCustomImageWidget::setNumberOfLayers(int layers) { intNumLayers->setValue(layers); } quint8 KisCustomImageWidget::backgroundOpacity() const { qint32 opacity = sliderOpacity->value(); if (!opacity) return 0; return (opacity * 255) / 100; } void KisCustomImageWidget::setBackgroundOpacity(quint8 value) { sliderOpacity->setValue((value * 100) / 255); } void KisCustomImageWidget::clipboardDataChanged() { } void KisCustomImageWidget::fillPredefined() { cmbPredefined->clear(); m_predefined.clear(); cmbPredefined->addItem(""); QStringList definitions = KoResourcePaths::findAllResources("data", "predefined_image_sizes/*.predefinedimage", KoResourcePaths::Recursive); definitions.sort(); if (!definitions.empty()) { Q_FOREACH (const QString &definition, definitions) { QFile f(definition); f.open(QIODevice::ReadOnly); if (f.exists()) { QString xml = QString::fromUtf8(f.readAll()); KisPropertiesConfigurationSP predefined = new KisPropertiesConfiguration; predefined->fromXML(xml); if (predefined->hasProperty("name") && predefined->hasProperty("width") && predefined->hasProperty("height") && predefined->hasProperty("resolution") && predefined->hasProperty("x-unit") && predefined->hasProperty("y-unit")) { m_predefined << predefined; cmbPredefined->addItem(predefined->getString("name")); } } } } cmbPredefined->setCurrentIndex(0); } void KisCustomImageWidget::predefinedClicked(int index) { if (index < 1 || index > m_predefined.size()) return; KisPropertiesConfigurationSP predefined = m_predefined[index - 1]; txtPredefinedName->setText(predefined->getString("name")); doubleResolution->setValue(predefined->getDouble("resolution")); cmbWidthUnit->setCurrentIndex(predefined->getInt("x-unit")); cmbHeightUnit->setCurrentIndex(predefined->getInt("y-unit")); widthUnitChanged(cmbWidthUnit->currentIndex()); heightUnitChanged(cmbHeightUnit->currentIndex()); doubleWidth->setValue(predefined->getDouble("width")); doubleHeight->setValue(predefined->getDouble("height")); changeDocumentInfoLabel(); } void KisCustomImageWidget::saveAsPredefined() { QString fileName = txtPredefinedName->text(); if (fileName.isEmpty()) { return; } QString saveLocation = KoResourcePaths::saveLocation("data", "predefined_image_sizes/", true); QFile f(saveLocation + '/' + fileName.replace(' ', '_').replace('(', '_').replace(')', '_').replace(':', '_') + ".predefinedimage"); f.open(QIODevice::WriteOnly | QIODevice::Truncate); KisPropertiesConfigurationSP predefined = new KisPropertiesConfiguration(); predefined->setProperty("name", txtPredefinedName->text()); predefined->setProperty("width", doubleWidth->value()); predefined->setProperty("height", doubleHeight->value()); predefined->setProperty("resolution", doubleResolution->value()); predefined->setProperty("x-unit", cmbWidthUnit->currentIndex()); predefined->setProperty("y-unit", cmbHeightUnit->currentIndex()); QString xml = predefined->toXML(); f.write(xml.toUtf8()); f.flush(); f.close(); int i = 0; bool found = false; Q_FOREACH (KisPropertiesConfigurationSP pr, m_predefined) { if (pr->getString("name") == txtPredefinedName->text()) { found = true; break; } ++i; } if (found) { m_predefined[i] = predefined; } else { m_predefined.append(predefined); cmbPredefined->addItem(txtPredefinedName->text()); } } void KisCustomImageWidget::setLandscape() { if (doubleWidth->value() < doubleHeight->value()) { switchWidthHeight(); } } void KisCustomImageWidget::setPortrait() { if (doubleWidth->value() > doubleHeight->value()) { switchWidthHeight(); } } void KisCustomImageWidget::switchWidthHeight() { double width = doubleWidth->value(); double height = doubleHeight->value(); doubleHeight->blockSignals(true); doubleWidth->blockSignals(true); cmbWidthUnit->blockSignals(true); cmbHeightUnit->blockSignals(true); doubleWidth->setValue(height); doubleHeight->setValue(width); cmbWidthUnit->setCurrentIndex(m_heightUnit.indexInListForUi(KoUnit::ListAll)); cmbHeightUnit->setCurrentIndex(m_widthUnit.indexInListForUi(KoUnit::ListAll)); doubleHeight->blockSignals(false); doubleWidth->blockSignals(false); cmbWidthUnit->blockSignals(false); cmbHeightUnit->blockSignals(false); switchPortraitLandscape(); widthChanged(doubleWidth->value()); heightChanged(doubleHeight->value()); changeDocumentInfoLabel(); } void KisCustomImageWidget::switchPortraitLandscape() { if(doubleWidth->value() > doubleHeight->value()) bnLandscape->setChecked(true); else bnPortrait->setChecked(true); } void KisCustomImageWidget::changeDocumentInfoLabel() { int layerSize = doubleWidth->value()*doubleHeight->value(); const KoColorSpace *cs = colorSpaceSelector->currentColorSpace(); int bitSize = 8*cs->pixelSize(); //pixelsize is in bytes. layerSize = layerSize*cs->pixelSize(); QString byte = "bytes"; - if (layerSize>1000) { - layerSize/=1000; - byte = "kB"; + if (layerSize>1024) { + layerSize/=1024; + byte = "KB"; } - if (layerSize>1000) { - layerSize/=1000; - byte = "mB"; + if (layerSize>1024) { + layerSize/=1024; + byte = "MB"; } - if (layerSize>1000) { - layerSize/=1000; - byte = "gB"; + if (layerSize>1024) { + layerSize/=1024; + byte = "GB"; } - QString text = QString("This document will be %1x%2 px %4, which means the pixel size is %3 bit, a single paint layer will thus take up %5 %6 of ram.") + QString text = QString("This document will be %1x%2 px %4, which means the pixel size is %3 bit, a single paint layer will thus take up %5 %6 of RAM.") .arg(doubleWidth->value()) .arg(doubleHeight->value()) .arg(bitSize) .arg(cs->name()) .arg(layerSize) .arg(byte); lblDocumentInfo->setText(text); } diff --git a/libs/ui/widgets/kis_paintop_preset_icon_library.cpp b/libs/ui/widgets/kis_paintop_preset_icon_library.cpp new file mode 100644 index 0000000000..ceb7ca9caf --- /dev/null +++ b/libs/ui/widgets/kis_paintop_preset_icon_library.cpp @@ -0,0 +1,166 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Wolthera van Hövell tot Westerflier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "kis_paintop_preset_icon_library.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +KisPaintopPresetIconLibrary::KisPaintopPresetIconLibrary(QWidget *parent): QWidget(parent), ui(new Ui_wdgpreseticonlibrary) +{ + ui->setupUi(this); + + ui->sldHue->setRange(0.0,360.0,1); + ui->sldHue->setSingleStep(1.0); + ui->sldHue->setPrefix(i18n("Hue:")); + + ui->sldSat->setRange(-50.0,50.0,1); + ui->sldSat->setSingleStep(1.0); + ui->sldSat->setPrefix(i18n("Saturation:")); + + ui->sldLevels->setRange(-50.0,50.0,1); + ui->sldLevels->setSingleStep(1.0); + ui->sldLevels->setPrefix(i18n("Mid-gray level:")); + + + m_baseModel = new QStandardItemModel(); + ui->vwBase->setModel(m_baseModel); + + m_optionalModel = new QStandardItemModel(); + ui->vwOptional->setModel(m_optionalModel); + + QStringList background_paths = KoResourcePaths::findAllResources("data", "preset_icons/*.png"); + if (background_paths.size()>0) { + m_background.load(background_paths.at(0)); + m_background = m_background.scaled(200, 200); + } + ui->lblIconPreview->setPixmap(QPixmap::fromImage(m_background)); + + + //empty default: + + QImage empty = QImage(200, 200, QImage::Format_ARGB32); + empty.fill(Qt::transparent); + m_baseModel->appendRow(new QStandardItem(QIcon(QPixmap::fromImage(empty)), NULL)); + + QStringList toolIcon_paths = KoResourcePaths::findAllResources("data", "preset_icons/tool_icons/*.png"); + for (int i=0; iappendRow(image); + } + + + empty = QImage(40, 40, QImage::Format_ARGB32); + empty.fill(Qt::transparent); + m_optionalModel->appendRow(new QStandardItem(QIcon(QPixmap::fromImage(empty)), NULL)); + + QStringList emblemIcon_paths = KoResourcePaths::findAllResources("data", "preset_icons/emblem_icons/*.png"); + for (int i=0; iappendRow(image); + } + + connect(ui->vwBase->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection &)), this, SLOT(updateIcon())); + connect(ui->vwOptional->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection &)), this, SLOT(updateIcon())); + connect(ui->sldHue, SIGNAL(valueChanged(qreal)), this, SLOT(updateIcon())); + connect(ui->sldSat, SIGNAL(valueChanged(qreal)), this, SLOT(updateIcon())); + connect(ui->sldLevels, SIGNAL(valueChanged(qreal)), this, SLOT(updateIcon())); +} + +KisPaintopPresetIconLibrary::~KisPaintopPresetIconLibrary() +{ + delete ui; + m_optionalModel->clear(); + delete m_optionalModel; + m_baseModel->clear(); + delete m_baseModel; +} + +QImage KisPaintopPresetIconLibrary::getImage() +{ + QImage preset = m_background; + QImage base = QImage(200, 200, QImage::Format_ARGB32); + base.fill(Qt::transparent); + if (ui->vwBase->currentIndex().isValid()) { + base = m_baseModel->itemFromIndex(ui->vwBase->currentIndex())->icon().pixmap(QSize(200, 200)).toImage(); + } + if (ui->sldHue->value()>0 || ui->sldSat->value()!=0.0 || ui->sldLevels->value()!=0.0) { + base = hueTransform(base); + } + QImage optional = QImage(40, 40, QImage::Format_ARGB32); + optional.fill(Qt::transparent); + if (ui->vwOptional->currentIndex().isValid()) { + optional = m_optionalModel->itemFromIndex(ui->vwOptional->currentIndex())->icon().pixmap(QSize(40, 40)).toImage(); + } + + QPainter p(&preset); + p.drawImage(0, 0, base); + int x = 35; + int y = 31; + p.drawImage(x, y, optional); + return preset; +} + +QImage KisPaintopPresetIconLibrary::hueTransform(QImage img) +{ + //This is a super simple levels operation instead of a regular lightness adjustment. + //This is so that we can keep the contrasts in the icon instead of making it + //just darker or lighter. + QVector values(256); + values.fill(255); + int level = qMax(qMin( 28 + ( (int(ui->sldLevels->value()) + 50) * 2), 255), 0); + for (int v = 0; v < level; v++) { + values[v] = int((128.0 / qreal(level))*v); + } + for (int v = level; v < 255; v++) { + values[v] = qMax(qMin(int( (128.0 / qreal(255 - level)) *(v - level)+128.0), 255), 0); + } + + //This is very slow by Krita standards, but we cannot get hsv transforms, ad the image is only 200x200. + for (int x = 0; x < img.width(); x++) { + for (int y = 0; y < img.height(); y++) { + QColor c = img.pixelColor(x, y); + int hue = c.hslHue()+(int)ui->sldHue->value(); + if (hue > 360) { + hue -= 360; + } + int sat = qMax(qMin(c.hslSaturation()+int(ui->sldSat->value() * (255/100)), 255), 0); + c.setHsl(hue, sat, values.at(c.lightness()), c.alpha()); + img.setPixelColor(x, y, c); + } + } + return img; +} + +void KisPaintopPresetIconLibrary::updateIcon() +{ + ui->lblIconPreview->setPixmap(QPixmap::fromImage(getImage())); +} + + diff --git a/libs/ui/widgets/kis_paintop_preset_icon_library.h b/libs/ui/widgets/kis_paintop_preset_icon_library.h new file mode 100644 index 0000000000..fd7f3dbef8 --- /dev/null +++ b/libs/ui/widgets/kis_paintop_preset_icon_library.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Wolthera van Hövell tot Westerflier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KIS_PAINTOP_PRESET_ICON_LIBRARY_H +#define KIS_PAINTOP_PRESET_ICON_LIBRARY_H + +#include +#include +#include "ui_wdgpreseticonlibrary.h" + +class Ui_wdgpreseticonlibrary; + +class KisPaintopPresetIconLibrary : public QWidget +{ + Q_OBJECT +public: + KisPaintopPresetIconLibrary(QWidget *parent); + ~KisPaintopPresetIconLibrary(); + + Ui_wdgpreseticonlibrary *ui; + + QImage getImage(); +public Q_SLOTS: + QImage hueTransform(QImage img); + void updateIcon(); + +private: + QStandardItemModel *m_baseModel; + QStandardItemModel *m_optionalModel; + QImage m_background; +}; + +#endif // KIS_PAINTOP_PRESET_ICON_LIBRARY_H diff --git a/libs/ui/widgets/kis_paintop_presets_save.cpp b/libs/ui/widgets/kis_paintop_presets_save.cpp index 174b6a46cf..28be44bf56 100644 --- a/libs/ui/widgets/kis_paintop_presets_save.cpp +++ b/libs/ui/widgets/kis_paintop_presets_save.cpp @@ -1,228 +1,254 @@ /* This file is part of the KDE project * Copyright (C) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "widgets/kis_paintop_presets_save.h" #include #include #include +#include +#include #include #include "KisImportExportManager.h" #include "QDesktopServices" #include "kis_resource_server_provider.h" +#include KisPresetSaveWidget::KisPresetSaveWidget(QWidget * parent) : KisPaintOpPresetSaveDialog(parent) { // this is setting the area we will "capture" for saving the brush preset. It can potentially be a different // area that the entire scratchpad brushPresetThumbnailWidget->setCutoutOverlayRect(QRect(0, 0, brushPresetThumbnailWidget->height(), brushPresetThumbnailWidget->width())); // we will default to reusing the previous preset thumbnail // have that checked by default, hide the other elements, and load the last preset image connect(clearBrushPresetThumbnailButton, SIGNAL(clicked(bool)), brushPresetThumbnailWidget, SLOT(fillDefault())); connect(loadImageIntoThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromFile())); connect(loadScratchPadThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadScratchpadThumbnail())); connect(loadExistingThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadExistingThumbnail())); + connect(loadIconLibraryThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromLibrary())); connect(savePresetButton, SIGNAL(clicked(bool)), this, SLOT(savePreset())); connect(cancelButton, SIGNAL(clicked(bool)), this, SLOT(close())); } KisPresetSaveWidget::~KisPresetSaveWidget() { } void KisPresetSaveWidget::scratchPadSetup(KisCanvasResourceProvider* resourceProvider) { m_resourceProvider = resourceProvider; brushPresetThumbnailWidget->setupScratchPad(m_resourceProvider, Qt::white); } void KisPresetSaveWidget::showDialog() { setModal(true); // set the name of the current brush preset area. KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); // UI will look a bit different if we are saving a new brush if (m_isSavingNewBrush) { setWindowTitle(i18n("Save New Brush Preset")); newBrushNameTexField->setVisible(true); clearBrushPresetThumbnailButton->setVisible(true); loadImageIntoThumbnailButton->setVisible(true); currentBrushNameLabel->setVisible(false); if (preset) { newBrushNameTexField->setText(preset->name().append(" ").append(i18n("Copy"))); } } else { setWindowTitle(i18n("Save Brush Preset")); if (preset) { currentBrushNameLabel->setText(preset->name()); } newBrushNameTexField->setVisible(false); currentBrushNameLabel->setVisible(true); } brushPresetThumbnailWidget->paintPresetImage(); show(); } void KisPresetSaveWidget::loadImageFromFile() { // create a dialog to retrieve an image file. KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); QString filename = dialog.filename(); // the filename() returns the entire path & file name, not just the file name if (filename != "") { // empty if "cancel" is pressed // take that file and load it into the thumbnail are const QImage imageToLoad(filename); brushPresetThumbnailWidget->fillTransparent(); // clear the background in case our new image has transparency brushPresetThumbnailWidget->paintCustomImage(imageToLoad); } } void KisPresetSaveWidget::loadScratchpadThumbnail() { brushPresetThumbnailWidget->paintCustomImage(scratchPadThumbnailArea); } void KisPresetSaveWidget::loadExistingThumbnail() { brushPresetThumbnailWidget->paintPresetImage(); } +void KisPresetSaveWidget::loadImageFromLibrary() +{ + //add dialog code here. + QDialog *dlg = new QDialog(this); + dlg->setWindowTitle(i18n("Preset Icon Library")); + QVBoxLayout *layout = new QVBoxLayout(); + dlg->setLayout(layout); + KisPaintopPresetIconLibrary *libWidget = new KisPaintopPresetIconLibrary(dlg); + layout->addWidget(libWidget); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, dlg); + connect(buttons, SIGNAL(accepted()), dlg, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), dlg, SLOT(reject())); + layout->addWidget(buttons); + + //if dialog accepted, get image. + if (dlg->exec()==QDialog::Accepted) { + + QImage presetImage = libWidget->getImage(); + brushPresetThumbnailWidget->paintCustomImage(presetImage); + } +} + void KisPresetSaveWidget::setFavoriteResourceManager(KisFavoriteResourceManager * favManager) { m_favoriteResourceManager = favManager; } void KisPresetSaveWidget::savePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); if (!curPreset) return; m_favoriteResourceManager->setBlockUpdates(true); KisPaintOpPresetSP oldPreset = curPreset->clone(); oldPreset->load(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); // if we are saving a new brush, use what we type in for the input QString presetName = m_isSavingNewBrush ? newBrushNameTexField->text() : curPreset->name(); QString currentPresetFileName = saveLocation + presetName + curPreset->defaultFileExtension(); // if the preset already exists, make a back up of it if (rServer->resourceByName(presetName)) { QString currentDate = QDate::currentDate().toString(Qt::ISODate); QString currentTime = QTime::currentTime().toString(Qt::ISODate); QString presetFilename = saveLocation + presetName + "_backup_" + currentDate + "-" + currentTime + oldPreset->defaultFileExtension(); oldPreset->setFilename(presetFilename); oldPreset->setName(presetName); oldPreset->setPresetDirty(false); oldPreset->setValid(true); // add resource to the blacklist rServer->addResource(oldPreset); rServer->removeResourceAndBlacklist(oldPreset.data()); QStringList tags; tags = rServer->assignedTagsList(curPreset.data()); Q_FOREACH (const QString & tag, tags) { rServer->addTag(oldPreset.data(), tag); } } if (m_isSavingNewBrush) { KisPaintOpPresetSP newPreset = curPreset->clone(); newPreset->setFilename(currentPresetFileName); newPreset->setName(presetName); newPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); newPreset->setPresetDirty(false); newPreset->setValid(true); rServer->addResource(newPreset); // trying to get brush preset to load after it is created emit resourceSelected(newPreset.data()); } else { if (curPreset->filename().contains(saveLocation)==false || curPreset->filename().contains(presetName)==false) { rServer->removeResourceAndBlacklist(curPreset.data()); curPreset->setFilename(currentPresetFileName); curPreset->setName(presetName); } if (!rServer->resourceByFilename(curPreset->filename())){ //this is necessary so that we can get the preset afterwards. rServer->addResource(curPreset, false, false); rServer->removeFromBlacklist(curPreset.data()); } curPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); curPreset->save(); curPreset->load(); } // HACK ALERT! the server does not notify the observers // automatically, so we need to call theupdate manually! rServer->tagCategoryMembersChanged(); m_favoriteResourceManager->setBlockUpdates(false); close(); // we are done... so close the save brush dialog } void KisPresetSaveWidget::saveScratchPadThumbnailArea(QImage image) { scratchPadThumbnailArea = image; } void KisPresetSaveWidget::isSavingNewBrush(bool newBrush) { m_isSavingNewBrush = newBrush; } #include "moc_kis_paintop_presets_save.cpp" diff --git a/libs/ui/widgets/kis_paintop_presets_save.h b/libs/ui/widgets/kis_paintop_presets_save.h index 5f03a95253..a334a1954c 100644 --- a/libs/ui/widgets/kis_paintop_presets_save.h +++ b/libs/ui/widgets/kis_paintop_presets_save.h @@ -1,79 +1,80 @@ /* This file is part of the KDE project * Copyright (C) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_PRESETS_SAVE_H #define KIS_PAINTOP_PRESETS_SAVE_H #include #include #include "ui_wdgsavebrushpreset.h" #include "kis_canvas_resource_provider.h" #include "kis_favorite_resource_manager.h" class KisPaintOpPresetSaveDialog : public QDialog , public Ui::WdgSaveBrushPreset { Q_OBJECT public: KisPaintOpPresetSaveDialog(QWidget* parent) : QDialog(parent) { setupUi(this); } }; class KisPresetSaveWidget : public KisPaintOpPresetSaveDialog { Q_OBJECT public: KisPresetSaveWidget(QWidget* parent); virtual ~KisPresetSaveWidget(); void showDialog(); void isSavingNewBrush(bool newBrush); void scratchPadSetup(KisCanvasResourceProvider* resourceProvider); void saveScratchPadThumbnailArea(const QImage image); KisCanvasResourceProvider* m_resourceProvider; void setFavoriteResourceManager(KisFavoriteResourceManager * favManager); Q_SIGNALS: void resourceSelected(KoResource* resource); public Q_SLOTS: void loadImageFromFile(); void savePreset(); void loadScratchpadThumbnail(); void loadExistingThumbnail(); + void loadImageFromLibrary(); private: bool m_isSavingNewBrush; KisFavoriteResourceManager * m_favoriteResourceManager; QImage scratchPadThumbnailArea; }; #endif diff --git a/libs/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp index b3c1b9e81f..34965f5d9a 100644 --- a/libs/ui/widgets/kis_preset_chooser.cpp +++ b/libs/ui/widgets/kis_preset_chooser.cpp @@ -1,355 +1,361 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_preset_chooser.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include "KoResourceItemView.h" #include #include #include "kis_resource_server_provider.h" #include "kis_global.h" #include "kis_slider_spin_box.h" #include "kis_config.h" #include "kis_config_notifier.h" #include /// The resource item delegate for rendering the resource preview class KisPresetDelegate : public QAbstractItemDelegate { public: KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {} ~KisPresetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } void setShowText(bool showText) { m_showText = showText; } void setUseDirtyPresets(bool value) { m_useDirtyPresets = value; } private: bool m_showText; bool m_useDirtyPresets; }; void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (! index.isValid()) return; KisPaintOpPreset* preset = static_cast(index.internalPointer()); QImage preview = preset->image(); if(preview.isNull()) { return; } QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (!m_showText) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { QSize pixSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); // Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small QString dirtyPresetIndicator = QString(""); if (m_useDirtyPresets && preset->isPresetDirty()) { dirtyPresetIndicator = QString("*"); } qreal brushSize = preset->settings()->paintOpSize(); QString brushSizeText; // Disable displayed decimal precision beyond a certain brush size if (brushSize < 100) { brushSizeText = QString::number(brushSize, 'g', 3); } else { brushSizeText = QString::number(brushSize, 'f', 0); } painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, brushSizeText); painter->drawText(pixSize.width() + 40, option.rect.y() + option.rect.height() - 10, preset->name().append(dirtyPresetIndicator)); } if (m_useDirtyPresets && preset->isPresetDirty()) { const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset")); QPixmap pixmap = icon.pixmap(QSize(15,15)); painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap); } if (!preset->settings() || !preset->settings()->isValid()) { const QIcon icon = KisIconUtils::loadIcon("broken-preset"); icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25)); } if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(1.0); painter->fillRect(option.rect, option.palette.highlight()); // highlight is not strong enough to pick out preset. draw border around it. painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets painter->drawRect(selectedBorder); } painter->restore(); } class KisPresetProxyAdapter : public KisPaintOpPresetResourceServerAdapter { public: KisPresetProxyAdapter(KisPaintOpPresetResourceServer* resourceServer) : KisPaintOpPresetResourceServerAdapter(resourceServer) { setSortingEnabled(true); } ~KisPresetProxyAdapter() override {} QList< KoResource* > resources() override { QList serverResources = KisPaintOpPresetResourceServerAdapter::resources(); if (m_paintopID.isEmpty()) { return serverResources; } QList resources; Q_FOREACH (KoResource *resource, serverResources) { KisPaintOpPreset *preset = dynamic_cast(resource); if (preset && preset->paintOp().id() == m_paintopID) { resources.append(preset); } } return resources; } ///Set id for paintop to be accept by the proxy model, if not filter is set all ///presets will be shown. void setPresetFilter(const QString& paintOpId) { m_paintopID = paintOpId; invalidate(); } ///Resets the model connected to the adapter void invalidate() { emitRemovingResource(0); } QString currentPaintOpId() const { return m_paintopID; } private: QString m_paintopID; }; KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QVBoxLayout * layout = new QVBoxLayout(this); layout->setMargin(0); KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); m_adapter = QSharedPointer(new KisPresetProxyAdapter(rserver)); m_chooser = new KoResourceItemChooser(m_adapter, this); m_chooser->setObjectName("ResourceChooser"); m_chooser->setColumnCount(10); m_chooser->setRowHeight(50); m_delegate = new KisPresetDelegate(this); m_chooser->setItemDelegate(m_delegate); m_chooser->setSynced(true); layout->addWidget(m_chooser); + KisConfig cfg; + m_chooser->configureKineticScrolling(cfg.kineticScrollingGesture(), + cfg.kineticScrollingSensitivity(), + cfg.kineticScrollingScrollbar()); + connect(m_chooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(resourceSelected(KoResource*))); connect(m_chooser, SIGNAL(resourceClicked(KoResource*)), this, SIGNAL(resourceClicked(KoResource*))); m_mode = THUMBNAIL; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(notifyConfigChanged())); notifyConfigChanged(); } KisPresetChooser::~KisPresetChooser() { } void KisPresetChooser::showButtons(bool show) { m_chooser->showButtons(show); } void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisPresetChooser::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisPresetChooser::notifyConfigChanged() { KisConfig cfg; m_delegate->setUseDirtyPresets(cfg.useDirtyPresets()); setIconSize(cfg.presetIconSize()); updateViewSettings(); } void KisPresetChooser::updateViewSettings() { if (m_mode == THUMBNAIL) { m_chooser->setSynced(true); m_delegate->setShowText(false); } else if (m_mode == DETAIL) { m_chooser->setSynced(false); m_chooser->setColumnCount(1); m_chooser->setColumnWidth(m_chooser->width()); KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); m_chooser->setRowHeight(chooserSync->baseLength()); m_delegate->setShowText(true); } else if (m_mode == STRIP) { m_chooser->setSynced(false); m_chooser->setRowCount(1); m_chooser->itemView()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_chooser->itemView()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7); m_delegate->setShowText(false); } } void KisPresetChooser::setCurrentResource(KoResource *resource) { /** * HACK ALERT: here we use a direct call to an adapter to notify the view * that the preset might have changed its dirty state. This state * doesn't affect the filtering so the server's cache must not be * invalidated! * * Ideally, we should call some method of KoResourceServer instead, * but ut seems like a bit too much effort for such a small fix. */ if (resource == currentResource()) { KisPresetProxyAdapter *adapter = static_cast(m_adapter.data()); KisPaintOpPreset *preset = dynamic_cast(resource); if (preset) { adapter->resourceChangedNoCacheInvalidation(preset); } } m_chooser->setCurrentResource(resource); } KoResource* KisPresetChooser::currentResource() const { return m_chooser->currentResource(); } void KisPresetChooser::showTaggingBar(bool show) { m_chooser->showTaggingBar(show); } KoResourceItemChooser *KisPresetChooser::itemChooser() { return m_chooser; } void KisPresetChooser::setPresetFilter(const QString& paintOpId) { KisPresetProxyAdapter *adapter = static_cast(m_adapter.data()); if (adapter->currentPaintOpId() != paintOpId) { adapter->setPresetFilter(paintOpId); updateViewSettings(); } } void KisPresetChooser::setIconSize(int newSize) { KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); chooserSync->setBaseLength(newSize); updateViewSettings(); } int KisPresetChooser::iconSize() { KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); return chooserSync->baseLength(); } void KisPresetChooser::saveIconSize() { // save icon size KisConfig cfg; cfg.setPresetIconSize(iconSize()); } diff --git a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp index 60fddd99dc..e33b780867 100644 --- a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp +++ b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp @@ -1,1472 +1,1468 @@ /* Copyright 2008 Brad Hards Copyright 2009 - 2010 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "EmfOutputPainterStrategy.h" #include #include #include "EmfObjects.h" #define DEBUG_EMFPAINT 0 #define DEBUG_PAINTER_TRANSFORM 0 namespace Libemf { static QPainter::CompositionMode rasteropToQtComposition(long rop); // ================================================================ // Class OutputPainterStrategy OutputPainterStrategy::OutputPainterStrategy() : m_header( 0 ) , m_path( 0 ) , m_currentlyBuildingPath( false ) , m_fillRule(Qt::OddEvenFill) , m_mapMode(MM_TEXT) , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT , m_currentCoords() { m_painter = 0; m_painterSaves = 0; m_outputSize = QSize(); m_keepAspectRatio = true; } OutputPainterStrategy::OutputPainterStrategy(QPainter &painter, QSize &size, bool keepAspectRatio) : m_header( 0 ) , m_path( 0 ) , m_currentlyBuildingPath( false ) , m_windowExtIsSet(false) , m_viewportExtIsSet(false) , m_windowViewportIsSet(false) , m_fillRule(Qt::OddEvenFill) , m_mapMode(MM_TEXT) , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT , m_currentCoords() { m_painter = &painter; m_painterSaves = 0; m_outputSize = size; m_keepAspectRatio = keepAspectRatio; } OutputPainterStrategy::~OutputPainterStrategy() { delete m_header; delete m_path; } void OutputPainterStrategy::paintBounds(const Header *header) { // The rectangle is in device coordinates. QRectF rect(header->bounds()); m_painter->save(); // Draw a simple cross in a rectangle to show the bounds. m_painter->setPen(QPen(QColor(172, 196, 206), 0)); m_painter->drawRect(rect); m_painter->drawLine(rect.topLeft(), rect.bottomRight()); m_painter->drawLine(rect.bottomLeft(), rect.topRight()); m_painter->restore(); } void OutputPainterStrategy::init( const Header *header ) { // Save the header since we need the frame and bounds inside the drawing. m_header = new Header(*header); QSize headerBoundsSize = header->bounds().size(); #if DEBUG_EMFPAINT debugVectorImage << "----------------------------------------------------------------------"; debugVectorImage << "Shape size =" << m_outputSize.width() << m_outputSize.height() << " pt"; debugVectorImage << "----------------------------------------------------------------------"; debugVectorImage << "Boundary box (dev units) =" << header->bounds().x() << header->bounds().y() << header->bounds().width() << header->bounds().height(); debugVectorImage << "Frame (phys size) =" << header->frame().x() << header->frame().y() << header->frame().width() << header->frame().height() << " *0.01 mm"; debugVectorImage << "Device =" << header->device().width() << header->device().height(); debugVectorImage << "Millimeters =" << header->millimeters().width() << header->millimeters().height(); #endif #if DEBUG_PAINTER_TRANSFORM printPainterTransform("In init, before save:"); #endif // This is restored in cleanup(). m_painter->save(); // Calculate how much the painter should be resized to fill the // outputSize with output. qreal scaleX = qreal( m_outputSize.width() ) / headerBoundsSize.width(); qreal scaleY = qreal( m_outputSize.height() ) / headerBoundsSize.height(); if ( m_keepAspectRatio ) { // Use the smaller value so that we don't get an overflow in // any direction. if ( scaleX > scaleY ) scaleX = scaleY; else scaleY = scaleX; } #if DEBUG_EMFPAINT debugVectorImage << "scale = " << scaleX << ", " << scaleY; #endif // Transform the EMF object so that it fits in the shape. The // topleft of the EMF will be the top left of the shape. m_painter->scale( scaleX, scaleY ); m_painter->translate(-header->bounds().left(), -header->bounds().top()); #if DEBUG_PAINTER_TRANSFORM printPainterTransform("after fitting into shape"); #endif // Save the scale so that we can use it when setting lineWidth. m_outputScale = (scaleX + scaleY) / 2; // Calculate translation if we should center the EMF in the // area and keep the aspect ratio. #if 0 // Should apparently be upper left. See bug 265868 if ( m_keepAspectRatio ) { m_painter->translate((m_outputSize.width() / scaleX - headerBoundsSize.width()) / 2, (m_outputSize.height() / scaleY - headerBoundsSize.height()) / 2); #if DEBUG_PAINTER_TRANSFORM printPainterTransform("after translation for keeping center in the shape"); #endif } #endif m_outputTransform = m_painter->transform(); m_worldTransform = QTransform(); // For calculations of window / viewport during the painting m_windowOrg = QPoint(0, 0); m_viewportOrg = QPoint(0, 0); m_windowExtIsSet = false; m_viewportExtIsSet = false; m_windowViewportIsSet = false; #if DEBUG_EMFPAINT paintBounds(header); #endif } void OutputPainterStrategy::cleanup( const Header *header ) { Q_UNUSED( header ); #if DEBUG_EMFPAINT if (m_painterSaves > 0) debugVectorImage << "WARNING: UNRESTORED DC's:" << m_painterSaves; #endif // Restore all the save()s that were done during the processing. for (int i = 0; i < m_painterSaves; ++i) m_painter->restore(); m_painterSaves = 0; // Restore the painter to what it was before init() was called. m_painter->restore(); } void OutputPainterStrategy::eof() { } void OutputPainterStrategy::setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << point << red << green << blue; #endif m_painter->save(); QPen pen; pen.setColor( QColor( red, green, blue ) ); m_painter->setPen( pen ); m_painter->drawPoint( point ); m_painter->restore(); } void OutputPainterStrategy::beginPath() { #if DEBUG_EMFPAINT debugVectorImage; #endif delete( m_path ); m_path = new QPainterPath; m_currentlyBuildingPath = true; } void OutputPainterStrategy::closeFigure() { #if DEBUG_EMFPAINT debugVectorImage; #endif m_path->closeSubpath(); } void OutputPainterStrategy::endPath() { #if DEBUG_EMFPAINT debugVectorImage; #endif m_path->setFillRule( m_fillRule ); m_currentlyBuildingPath = false; } void OutputPainterStrategy::saveDC() { #if DEBUG_EMFPAINT debugVectorImage; #endif // A little trick here: Save the worldTransform in the painter. // If we didn't do this, we would have to create a separate stack // for these. // // FIXME: We should collect all the parts of the DC that are not // stored in the painter and save them separately. QTransform savedTransform = m_painter->worldTransform(); m_painter->setWorldTransform(m_worldTransform); m_painter->save(); ++m_painterSaves; m_painter->setWorldTransform(savedTransform); } void OutputPainterStrategy::restoreDC( const qint32 savedDC ) { #if DEBUG_EMFPAINT debugVectorImage << savedDC; #endif // Note that savedDC is always negative for (int i = 0; i < -savedDC; ++i) { if (m_painterSaves > 0) { m_painter->restore(); --m_painterSaves; } else { debugVectorImage << "restoreDC(): try to restore painter without save" << savedDC - i; break; } } // We used a trick in saveDC() and stored the worldTransform in // the painter. Now restore the full transformation. m_worldTransform = m_painter->worldTransform(); QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setMetaRgn() { debugVectorImage << "EMR_SETMETARGN not yet implemented"; } // ---------------------------------------------------------------- // World Transform, Window and Viewport // General note about coordinate spaces and transforms: // // There are several coordinate spaces in use when drawing an EMF file: // 1. The object space, in which the objects' coordinates are expressed inside the EMF. // In general there are several of these. // 2. The page space, which is where they end up being painted in the EMF picture. // The union of these form the bounding box of the EMF. // 3. (possibly) the output space, where the EMF picture itself is placed // and/or scaled, rotated, etc // // The transform between spaces 1. and 2. is called the World Transform. // The world transform can be changed either through calls to change // the window or viewport or through calls to setWorldTransform() or // modifyWorldTransform(). // // The transform between spaces 2. and 3. is the transform that the QPainter // already contains when it is given to us. We need to save this and reapply // it after the world transform has changed. We call this transform the Output // Transform in lack of a better word. (Some sources call it the Device Transform.) // // An unanswered question: // // The file mp07_embedded_ppt.pptx in the test files contains the // following sequence of records: // - SetWorldTransform // - ModifyWorldTransform // - SetViewportOrg <-- doesn't change anything // - SetWindowOrg <-- doesn't change anything // - ExtTextOutw // // I was previously under the impression that whenever a // Set{Window,Viewport}{Org,Ext} record was encountered, the world // transform was supposed to be recalculated. But in this file, it // destroys the world transform. The question is which of the // following alternatives is true: -// +// // 1. The world transform should only be recalculated if the // Set{Window,Viewport}{Org,Ext} record actually changes anything. // // 2. The transformations set by {Set,Modify}WorldTransform records // should always be reapplied after a change in window or viewport. // // 3. Something else // // I have for now implemented option 1. See the FIXME's in // SetWindowOrg et al. // // Set Window and Viewport void OutputPainterStrategy::recalculateWorldTransform() { m_worldTransform = QTransform(); // If neither the window nor viewport extension is set, then there // is no way to perform the calculation. Just give up. if (!m_windowExtIsSet && !m_viewportExtIsSet) return; // Negative window extensions mean flip the picture. Handle this here. bool flip = false; qreal midpointX = 0.0; qreal midpointY = 0.0; qreal scaleX = 1.0; qreal scaleY = 1.0; if (m_windowExt.width() < 0) { midpointX = m_windowOrg.x() + m_windowExt.width() / qreal(2.0); scaleX = -1.0; flip = true; } if (m_windowExt.height() < 0) { midpointY = m_windowOrg.y() + m_windowExt.height() / qreal(2.0); scaleY = -1.0; flip = true; } if (flip) { //debugVectorImage << "Flipping" << midpointX << midpointY << scaleX << scaleY; m_worldTransform.translate(midpointX, midpointY); m_worldTransform.scale(scaleX, scaleY); m_worldTransform.translate(-midpointX, -midpointY); //debugVectorImage << "After flipping for window" << mWorldTransform; } // Update the world transform if both window and viewport are set... // FIXME: Check windowExt == 0 in any direction if (m_windowExtIsSet && m_viewportExtIsSet) { // Both window and viewport are set. qreal windowViewportScaleX = qreal(m_viewportExt.width()) / qreal(m_windowExt.width()); qreal windowViewportScaleY = qreal(m_viewportExt.height()) / qreal(m_windowExt.height()); m_worldTransform.translate(-m_windowOrg.x(), -m_windowOrg.y()); m_worldTransform.scale(windowViewportScaleX, windowViewportScaleY); m_worldTransform.translate(m_viewportOrg.x(), m_viewportOrg.y()); } // ...and apply it to the painter m_painter->setWorldTransform(m_worldTransform); m_windowViewportIsSet = true; // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setWindowOrgEx( const QPoint &origin ) { #if DEBUG_EMFPAINT debugVectorImage << origin; #endif // FIXME: See unanswered question at the start of this section. if (m_windowOrg == origin) { //debugVectorImage << "same origin as before"; return; } m_windowOrg = origin; recalculateWorldTransform(); } void OutputPainterStrategy::setWindowExtEx( const QSize &size ) { #if DEBUG_EMFPAINT debugVectorImage << size; #endif // FIXME: See unanswered question at the start of this section. if (m_windowExt == size) { //debugVectorImage << "same extension as before"; return; } m_windowExt = size; m_windowExtIsSet = true; recalculateWorldTransform(); } void OutputPainterStrategy::setViewportOrgEx( const QPoint &origin ) { #if DEBUG_EMFPAINT debugVectorImage << origin; #endif // FIXME: See unanswered question at the start of this section. if (m_viewportOrg == origin) { //debugVectorImage << "same origin as before"; return; } m_viewportOrg = origin; recalculateWorldTransform(); } void OutputPainterStrategy::setViewportExtEx( const QSize &size ) { #if DEBUG_EMFPAINT debugVectorImage << size; #endif // FIXME: See unanswered question at the start of this section. if (m_viewportExt == size) { //debugVectorImage << "same extension as before"; return; } m_viewportExt = size; m_viewportExtIsSet = true; recalculateWorldTransform(); } void OutputPainterStrategy::modifyWorldTransform( quint32 mode, float M11, float M12, float M21, float M22, float Dx, float Dy ) { #if DEBUG_EMFPAINT if (mode == MWT_IDENTITY) debugVectorImage << "Identity matrix"; else debugVectorImage << mode << M11 << M12 << M21 << M22 << Dx << Dy; #endif QTransform matrix( M11, M12, M21, M22, Dx, Dy); if ( mode == MWT_IDENTITY ) { m_worldTransform = QTransform(); } else if ( mode == MWT_LEFTMULTIPLY ) { m_worldTransform = matrix * m_worldTransform; } else if ( mode == MWT_RIGHTMULTIPLY ) { m_worldTransform = m_worldTransform * matrix; } else if ( mode == MWT_SET ) { m_worldTransform = matrix; } else { - warnVectorImage << "Unimplemented transform mode" << mode; + warnVectorImage << "Unimplemented transform mode" << mode; } // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setWorldTransform( float M11, float M12, float M21, float M22, float Dx, float Dy ) { #if DEBUG_EMFPAINT debugVectorImage << M11 << M12 << M21 << M22 << Dx << Dy; #endif QTransform matrix( M11, M12, M21, M22, Dx, Dy); m_worldTransform = matrix; // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } // ---------------------------------------------------------------- void OutputPainterStrategy::createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y, quint8 red, quint8 green, quint8 blue, quint8 reserved ) { Q_UNUSED( y ); Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << ihPen << hex << penStyle << dec << x << y << red << green << blue << reserved; #endif QPen pen; pen.setColor( QColor( red, green, blue ) ); if ( penStyle & PS_GEOMETRIC ) { - pen.setCosmetic( false ); + pen.setCosmetic( false ); } else { - pen.setCosmetic( true ); + pen.setCosmetic( true ); } switch ( penStyle & 0xF ) { case PS_SOLID: pen.setStyle( Qt::SolidLine ); break; case PS_DASH: pen.setStyle( Qt::DashLine ); break; case PS_DOT: pen.setStyle( Qt::DotLine ); break; case PS_DASHDOT: pen.setStyle( Qt::DashDotLine ); break; case PS_DASHDOTDOT: pen.setStyle( Qt::DashDotDotLine ); break; case PS_NULL: pen.setStyle( Qt::NoPen ); break; case PS_INSIDEFRAME: // FIXME: We don't properly support this pen.setStyle( Qt::SolidLine ); break; case PS_USERSTYLE: debugVectorImage << "UserStyle pen not yet supported, using SolidLine"; pen.setStyle( Qt::SolidLine ); break; case PS_ALTERNATE: debugVectorImage << "Alternate pen not yet supported, using DashLine"; pen.setStyle( Qt::DashLine ); break; default: debugVectorImage << "unexpected pen type, using SolidLine" << (penStyle & 0xF); pen.setStyle( Qt::SolidLine ); } switch ( penStyle & PS_ENDCAP_FLAT ) { case PS_ENDCAP_ROUND: pen.setCapStyle( Qt::RoundCap ); break; case PS_ENDCAP_SQUARE: pen.setCapStyle( Qt::SquareCap ); break; case PS_ENDCAP_FLAT: pen.setCapStyle( Qt::FlatCap ); break; default: debugVectorImage << "unexpected cap style, using SquareCap" << (penStyle & PS_ENDCAP_FLAT); pen.setCapStyle( Qt::SquareCap ); } pen.setWidthF(x * m_outputScale); m_objectTable.insert( ihPen, pen ); } void OutputPainterStrategy::createBrushIndirect( quint32 ihBrush, quint32 brushStyle, quint8 red, quint8 green, quint8 blue, quint8 reserved, quint32 brushHatch ) { Q_UNUSED( reserved ); Q_UNUSED( brushHatch ); #if DEBUG_EMFPAINT debugVectorImage << ihBrush << hex << brushStyle << dec << red << green << blue << reserved << brushHatch; #endif QBrush brush; switch ( brushStyle ) { case BS_SOLID: - brush.setStyle( Qt::SolidPattern ); - break; + brush.setStyle( Qt::SolidPattern ); + break; case BS_NULL: - brush.setStyle( Qt::NoBrush ); - break; + brush.setStyle( Qt::NoBrush ); + break; case BS_HATCHED: - brush.setStyle( Qt::CrossPattern ); - break; + brush.setStyle( Qt::CrossPattern ); + break; case BS_PATTERN: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; case BS_INDEXED: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; case BS_DIBPATTERN: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; case BS_DIBPATTERNPT: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; case BS_PATTERN8X8: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; case BS_DIBPATTERN8X8: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; case BS_MONOPATTERN: - Q_ASSERT( 0 ); - break; + Q_ASSERT( 0 ); + break; default: - Q_ASSERT( 0 ); + Q_ASSERT( 0 ); } brush.setColor( QColor( red, green, blue ) ); // TODO: Handle the BrushHatch enum. m_objectTable.insert( ihBrush, brush ); } void OutputPainterStrategy::createMonoBrush( quint32 ihBrush, Bitmap *bitmap ) { QImage pattern(bitmap->image()); QBrush brush(pattern); m_objectTable.insert( ihBrush, brush ); } void OutputPainterStrategy::extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW ) { QFont font( extCreateFontIndirectW.fontFace() ); font.setWeight( convertFontWeight( extCreateFontIndirectW.weight() ) ); if ( extCreateFontIndirectW.height() < 0 ) { - font.setPixelSize( -1 * extCreateFontIndirectW.height() ); + font.setPixelSize( -1 * extCreateFontIndirectW.height() ); } else if ( extCreateFontIndirectW.height() > 0 ) { font.setPixelSize( extCreateFontIndirectW.height() ); } // zero is "use a default size" which is effectively no-op here. // .snp files don't always provide 0x01 for italics if ( extCreateFontIndirectW.italic() != 0x00 ) { - font.setItalic( true ); + font.setItalic( true ); } if ( extCreateFontIndirectW.underline() != 0x00 ) { - font.setUnderline( true ); + font.setUnderline( true ); } m_objectTable.insert( extCreateFontIndirectW.ihFonts(), font ); } void OutputPainterStrategy::selectStockObject( const quint32 ihObject ) { #if DEBUG_EMFPAINT debugVectorImage << ihObject; #endif switch ( ihObject ) { case WHITE_BRUSH: - m_painter->setBrush( QBrush( Qt::white ) ); - break; + m_painter->setBrush( QBrush( Qt::white ) ); + break; case LTGRAY_BRUSH: - m_painter->setBrush( QBrush( Qt::lightGray ) ); - break; + m_painter->setBrush( QBrush( Qt::lightGray ) ); + break; case GRAY_BRUSH: - m_painter->setBrush( QBrush( Qt::gray ) ); - break; + m_painter->setBrush( QBrush( Qt::gray ) ); + break; case DKGRAY_BRUSH: - m_painter->setBrush( QBrush( Qt::darkGray ) ); - break; + m_painter->setBrush( QBrush( Qt::darkGray ) ); + break; case BLACK_BRUSH: - m_painter->setBrush( QBrush( Qt::black ) ); - break; + m_painter->setBrush( QBrush( Qt::black ) ); + break; case NULL_BRUSH: - m_painter->setBrush( QBrush() ); - break; + m_painter->setBrush( QBrush() ); + break; case WHITE_PEN: - m_painter->setPen( QPen( Qt::white ) ); - break; + m_painter->setPen( QPen( Qt::white ) ); + break; case BLACK_PEN: - m_painter->setPen( QPen( Qt::black ) ); - break; + m_painter->setPen( QPen( Qt::black ) ); + break; case NULL_PEN: - m_painter->setPen( QPen( Qt::NoPen ) ); - break; + m_painter->setPen( QPen( Qt::NoPen ) ); + break; case OEM_FIXED_FONT: case ANSI_FIXED_FONT: case SYSTEM_FIXED_FONT: { QFont font(QString("Fixed")); m_painter->setFont(font); break; } case ANSI_VAR_FONT: case DEFAULT_GUI_FONT: // Not sure if this is true, but it should work well { QFont font(QString("Helvetica")); // Could also be "System" m_painter->setFont(font); break; } - break; + break; case SYSTEM_FONT: - // TODO: handle this - break; + // TODO: handle this + break; case DEVICE_DEFAULT_FONT: - // TODO: handle this - break; + // TODO: handle this + break; case DEFAULT_PALETTE: - break; + break; case DC_BRUSH: // FIXME - break; + break; case DC_PEN: // FIXME - break; + break; default: - warnVectorImage << "Unexpected stock object:" << ( ihObject & 0x8000000 ); + warnVectorImage << "Unexpected stock object:" << ( ihObject & 0x8000000 ); } } void OutputPainterStrategy::selectObject( const quint32 ihObject ) { #if DEBUG_EMFPAINT debugVectorImage << hex << ihObject << dec; #endif if ( ihObject & 0x80000000 ) { - selectStockObject( ihObject ); + selectStockObject( ihObject ); } else { - QVariant obj = m_objectTable.value( ihObject ); - - switch ( obj.type() ) { - case QVariant::Pen : - m_painter->setPen( obj.value() ); - break; - case QVariant::Brush : - m_painter->setBrush( obj.value() ); - break; - case QVariant::Font : - m_painter->setFont( obj.value() ); - break; - default: - debugVectorImage << "Unexpected type:" << obj.typeName(); - } + QVariant obj = m_objectTable.value( ihObject ); + + switch ( obj.type() ) { + case QVariant::Pen : + m_painter->setPen( obj.value() ); + break; + case QVariant::Brush : + m_painter->setBrush( obj.value() ); + break; + case QVariant::Font : + m_painter->setFont( obj.value() ); + break; + default: + debugVectorImage << "Unexpected type:" << obj.typeName(); + } } } void OutputPainterStrategy::deleteObject( const quint32 ihObject ) { m_objectTable.take( ihObject ); } void OutputPainterStrategy::arc( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawArc( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::chord( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawChord( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::pie( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawPie( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::ellipse( const QRect &box ) { #if DEBUG_EMFPAINT debugVectorImage << box; #endif m_painter->drawEllipse( box ); } void OutputPainterStrategy::rectangle( const QRect &box ) { #if DEBUG_EMFPAINT debugVectorImage << box; #endif m_painter->drawRect( box ); } void OutputPainterStrategy::setMapMode( const quint32 mapMode ) { #if DEBUG_EMFPAINT debugVectorImage << "Set map mode:" << mapMode; #endif m_mapMode = (MapMode)mapMode; } void OutputPainterStrategy::setBkMode( const quint32 backgroundMode ) { #if DEBUG_EMFPAINT debugVectorImage << backgroundMode; #endif if ( backgroundMode == TRANSPARENT ) { m_painter->setBackgroundMode( Qt::TransparentMode ); } else if ( backgroundMode == OPAQUE ) { m_painter->setBackgroundMode( Qt::OpaqueMode ); } else { debugVectorImage << "EMR_SETBKMODE: Unexpected value -" << backgroundMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setPolyFillMode( const quint32 polyFillMode ) { #if DEBUG_EMFPAINT debugVectorImage << polyFillMode; #endif if ( polyFillMode == ALTERNATE ) { - m_fillRule = Qt::OddEvenFill; + m_fillRule = Qt::OddEvenFill; } else if ( polyFillMode == WINDING ) { - m_fillRule = Qt::WindingFill; + m_fillRule = Qt::WindingFill; } else { - debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode; - Q_ASSERT( 0 ); + debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode; + Q_ASSERT( 0 ); } } void OutputPainterStrategy::setLayout( const quint32 layoutMode ) { #if DEBUG_EMFPAINT debugVectorImage << layoutMode; #endif if ( layoutMode == LAYOUT_LTR ) { m_painter->setLayoutDirection( Qt::LeftToRight ); } else if ( layoutMode == LAYOUT_RTL ) { m_painter->setLayoutDirection( Qt::RightToLeft ); } else { debugVectorImage << "EMR_SETLAYOUT: Unexpected value -" << layoutMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setTextAlign( const quint32 textAlignMode ) { #if DEBUG_EMFPAINT debugVectorImage << textAlignMode; #endif m_textAlignMode = textAlignMode; } void OutputPainterStrategy::setTextColor( const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << red << green << blue << reserved; #endif m_textPen.setColor( QColor( red, green, blue ) ); } void OutputPainterStrategy::setBkColor( const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << red << green << blue << reserved; #endif m_painter->setBackground( QBrush( QColor( red, green, blue ) ) ); } #define DEBUG_TEXTOUT 0 void OutputPainterStrategy::extTextOut( const QRect &bounds, const EmrTextObject &textObject ) { const QPoint &referencePoint = textObject.referencePoint(); const QString &text = textObject.textString(); #if DEBUG_EMFPAINT debugVectorImage << "Ref point: " << referencePoint << "options: " << hex << textObject.options() << dec << "rectangle: " << textObject.rectangle() << "text: " << textObject.textString(); #endif int x = referencePoint.x(); int y = referencePoint.y(); // The TA_UPDATECP flag tells us to use the current position if (m_textAlignMode & TA_UPDATECP) { // (left, top) position = current logical position #if DEBUG_EMFPAINT debugVectorImage << "TA_UPDATECP: use current logical position"; #endif x = m_currentCoords.x(); y = m_currentCoords.y(); } QFontMetrics fm = m_painter->fontMetrics(); int textWidth = fm.width(text) + fm.descent(); // fm.width(text) isn't right with Italic text int textHeight = fm.height(); // Make (x, y) be the coordinates of the upper left corner of the // rectangle surrounding the text. // // FIXME: Handle RTL text. // Horizontal align. Default is TA_LEFT. if ((m_textAlignMode & TA_HORZMASK) == TA_CENTER) x -= (textWidth / 2); else if ((m_textAlignMode & TA_HORZMASK) == TA_RIGHT) x -= textWidth; // Vertical align. Default is TA_TOP if ((m_textAlignMode & TA_VERTMASK) == TA_BASELINE) y -= (textHeight - fm.descent()); else if ((m_textAlignMode & TA_VERTMASK) == TA_BOTTOM) { y -= textHeight; } #if DEBUG_EMFPAINT debugVectorImage << "textWidth = " << textWidth << "height = " << textHeight; debugVectorImage << "font = " << m_painter->font() << "pointSize = " << m_painter->font().pointSize() << "ascent = " << fm.ascent() << "descent = " << fm.descent() << "height = " << fm.height() << "leading = " << fm.leading(); debugVectorImage << "actual point = " << x << y; #endif // Debug code that paints a rectangle around the output area. #if DEBUG_TEXTOUT m_painter->save(); m_painter->setWorldTransform(m_outputTransform); m_painter->setPen(QPen(Qt::black, 0)); m_painter->drawRect(bounds); m_painter->restore(); #endif // Actual painting starts here. m_painter->save(); // Find out how much we have to scale the text to make it fit into // the output rectangle. Normally this wouldn't be necessary, but // when fonts are switched, the replacement fonts are sometimes // wider than the original fonts. QRect worldRect(m_worldTransform.mapRect(QRect(x, y, textWidth, textHeight))); //debugVectorImage << "rects:" << QRect(x, y, textWidth, textHeight) << worldRect; qreal scaleX = qreal(1.0); qreal scaleY = qreal(1.0); if (bounds.width() < worldRect.width()) scaleX = qreal(bounds.width()) / qreal(worldRect.width()); if (bounds.height() < worldRect.height()) scaleY = qreal(bounds.height()) / qreal(worldRect.height()); //debugVectorImage << "scale:" << scaleX << scaleY; if (scaleX < qreal(1.0) || scaleY < qreal(1.0)) { m_painter->translate(-x, -y); m_painter->scale(scaleX, scaleY); m_painter->translate(x / scaleX, y / scaleY); } // Use the special pen defined by mTextPen for text. QPen savePen = m_painter->pen(); m_painter->setPen(m_textPen); m_painter->drawText(int(x / scaleX), int(y / scaleY), textWidth, textHeight, Qt::AlignLeft|Qt::AlignTop, text); m_painter->setPen(savePen); m_painter->restore(); } void OutputPainterStrategy::moveToEx( const qint32 x, const qint32 y ) { #if DEBUG_EMFPAINT debugVectorImage << x << y; #endif if ( m_currentlyBuildingPath ) m_path->moveTo( QPoint( x, y ) ); else m_currentCoords = QPoint( x, y ); } void OutputPainterStrategy::lineTo( const QPoint &finishPoint ) { #if DEBUG_EMFPAINT debugVectorImage << finishPoint; #endif if ( m_currentlyBuildingPath ) m_path->lineTo( finishPoint ); else { m_painter->drawLine( m_currentCoords, finishPoint ); m_currentCoords = finishPoint; } } void OutputPainterStrategy::arcTo( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_path->arcTo( box, startAngle, spanAngle ); } void OutputPainterStrategy::polygon16( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif QVector pointVector = points.toVector(); m_painter->drawPolygon( pointVector.constData(), pointVector.size(), m_fillRule ); } void OutputPainterStrategy::polyLine( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif QVector pointVector = points.toVector(); m_painter->drawPolyline( pointVector.constData(), pointVector.size() ); } void OutputPainterStrategy::polyLine16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif polyLine( bounds, points ); } void OutputPainterStrategy::polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.size(); ++i ) { m_painter->drawPolygon( points[i].constData(), points[i].size(), m_fillRule ); } } void OutputPainterStrategy::polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.size(); ++i ) { m_painter->drawPolyline( points[i].constData(), points[i].size() ); } } void OutputPainterStrategy::polyLineTo16( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.count(); ++i ) { - m_path->lineTo( points[i] ); + m_path->lineTo( points[i] ); } } void OutputPainterStrategy::polyBezier16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif Q_UNUSED( bounds ); QPainterPath path; path.moveTo( points[0] ); for ( int i = 1; i < points.count(); i+=3 ) { - path.cubicTo( points[i], points[i+1], points[i+2] ); + path.cubicTo( points[i], points[i+1], points[i+2] ); } m_painter->drawPath( path ); } void OutputPainterStrategy::polyBezierTo16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif Q_UNUSED( bounds ); for ( int i = 0; i < points.count(); i+=3 ) { - m_path->cubicTo( points[i], points[i+1], points[i+2] ); + m_path->cubicTo( points[i], points[i+1], points[i+2] ); } } void OutputPainterStrategy::fillPath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->fillPath( *m_path, m_painter->brush() ); } void OutputPainterStrategy::strokeAndFillPath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->drawPath( *m_path ); } void OutputPainterStrategy::strokePath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->strokePath( *m_path, m_painter->pen() ); } void OutputPainterStrategy::setClipPath( const quint32 regionMode ) { #if DEBUG_EMFPAINT debugVectorImage << hex << regionMode << dec; #endif switch ( regionMode ) { case RGN_AND: m_painter->setClipPath( *m_path, Qt::IntersectClip ); break; - // QT5TODO: T477 Qt::UniteClip got removed, see https://bugreports.qt.io/browse/QTBUG-20140 -// case RGN_OR: -// m_painter->setClipPath( *m_path, Qt::UniteClip ); -// break; case RGN_COPY: m_painter->setClipPath( *m_path, Qt::ReplaceClip ); break; default: warnVectorImage << "Unexpected / unsupported clip region mode:" << regionMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::bitBlt( BitBltRecord &bitBltRecord ) { #if DEBUG_EMFPAINT debugVectorImage << bitBltRecord.xDest() << bitBltRecord.yDest() << bitBltRecord.cxDest() << bitBltRecord.cyDest() << hex << bitBltRecord.rasterOperation() << dec << bitBltRecord.bkColorSrc(); #endif QRect target( bitBltRecord.xDest(), bitBltRecord.yDest(), bitBltRecord.cxDest(), bitBltRecord.cyDest() ); // 0x00f00021 is the PatCopy raster operation which just fills a rectangle with a brush. // This seems to be the most common one. // // FIXME: Implement the rest of the raster operations. if (bitBltRecord.rasterOperation() == 0x00f00021) { // Would have been nice if we didn't have to pull out the // brush to use it with fillRect()... QBrush brush = m_painter->brush(); m_painter->fillRect(target, brush); } else if ( bitBltRecord.hasImage() ) { m_painter->drawImage( target, bitBltRecord.image() ); } } void OutputPainterStrategy::setStretchBltMode( const quint32 stretchMode ) { #if DEBUG_EMFPAINT debugVectorImage << hex << stretchMode << dec; #endif switch ( stretchMode ) { case 0x01: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ANDSCANS"; break; case 0x02: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ORSCANS"; break; case 0x03: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_DELETESCANS"; break; case 0x04: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_HALFTONE"; break; default: debugVectorImage << "EMR_STRETCHBLTMODE - unknown stretch mode:" << stretchMode; } } void OutputPainterStrategy::stretchDiBits( StretchDiBitsRecord &record ) { #if DEBUG_EMFPAINT debugVectorImage << "Bounds: " << record.bounds(); debugVectorImage << "Dest rect: " << record.xDest() << record.yDest() << record.cxDest() << record.cyDest(); debugVectorImage << "Src rect: " << record.xSrc() << record.ySrc() << record.cxSrc() << record.cySrc(); debugVectorImage << "Raster op: " << hex << record.rasterOperation() << dec; //<< record.bkColorSrc(); debugVectorImage << "usageSrc: " << record.usageSrc(); #endif QPoint targetPosition( record.xDest(), record.yDest() ); QSize targetSize( record.cxDest(), record.cyDest() ); QPoint sourcePosition( record.xSrc(), record.ySrc() ); QSize sourceSize( record.cxSrc(), record.cySrc() ); // special cases, from [MS-EMF] Section 2.3.1.7: // "This record specifies a mirror-image copy of the source bitmap to the // destination if the signs of the height or width fields differ. That is, // if cxSrc and cxDest have different signs, this record specifies a mirror // image of the source bitmap along the x-axis. If cySrc and cyDest have // different signs, this record specifies a mirror image of the source // bitmap along the y-axis." QRect target( targetPosition, targetSize ); QRect source( sourcePosition, sourceSize ); #if DEBUG_EMFPAINT //debugVectorImage << "image size" << record.image()->size(); debugVectorImage << "Before transformation:"; debugVectorImage << " target" << target; debugVectorImage << " source" << source; #endif if ( source.width() < 0 && target.width() > 0 ) { sourceSize.rwidth() *= -1; sourcePosition.rx() -= sourceSize.width(); source = QRect( sourcePosition, sourceSize ); } if ( source.width() > 0 && target.width() < 0 ) { targetSize.rwidth() *= -1; targetPosition.rx() -= targetSize.width(); target = QRect( targetPosition, targetSize ); } if ( source.height() < 0 && target.height() > 0 ) { sourceSize.rheight() *= -1; sourcePosition.ry() -= sourceSize.height(); source = QRect( sourcePosition, sourceSize ); } if ( source.height() > 0 && target.height() < 0 ) { targetSize.rheight() *= -1; targetPosition.ry() -= targetSize.height(); target = QRect( targetPosition, targetSize ); } #if DEBUG_EMFPAINT debugVectorImage << "After transformation:"; debugVectorImage << " target" << target; debugVectorImage << " source" << source; QImage image = record.image(); debugVectorImage << "Image" << image.size(); #endif QPainter::RenderHints oldRenderHints = m_painter->renderHints(); QPainter::CompositionMode oldCompMode = m_painter->compositionMode(); m_painter->setRenderHints(0); // Antialiasing makes composition modes invalid m_painter->setCompositionMode(rasteropToQtComposition(record.rasterOperation())); m_painter->drawImage(target, record.image(), source); m_painter->setCompositionMode(oldCompMode); m_painter->setRenderHints(oldRenderHints); } // ---------------------------------------------------------------- // Private functions void OutputPainterStrategy::printPainterTransform(const char *leadText) { QTransform transform; recalculateWorldTransform(); debugVectorImage << leadText << "world transform " << m_worldTransform << "incl output transform: " << m_painter->transform(); } qreal OutputPainterStrategy::angleFromArc( const QPoint ¢rePoint, const QPoint &radialPoint ) { double dX = radialPoint.x() - centrePoint.x(); double dY = centrePoint.y() - radialPoint.y(); // Qt angles are in degrees. atan2 returns radians return ( atan2( dY, dX ) * 180 / M_PI ); } qreal OutputPainterStrategy::angularSpan( const qreal startAngle, const qreal endAngle ) { qreal spanAngle = endAngle - startAngle; if ( spanAngle <= 0 ) { spanAngle += 360; } return spanAngle; } int OutputPainterStrategy::convertFontWeight( quint32 emfWeight ) { // FIXME: See how it's done in the wmf library and check if this is suitable here. if ( emfWeight == 0 ) { return QFont::Normal; } else if ( emfWeight <= 200 ) { return QFont::Light; } else if ( emfWeight <= 450 ) { return QFont::Normal; } else if ( emfWeight <= 650 ) { return QFont::DemiBold; } else if ( emfWeight <= 850 ) { return QFont::Bold; } else { return QFont::Black; } } static QPainter::CompositionMode rasteropToQtComposition(long rop) { // Code copied from filters/libkowmf/qwmf.cc // FIXME: Should be cleaned up /* TODO: Ternary raster operations 0x00C000CA dest = (source AND pattern) 0x00F00021 dest = pattern 0x00FB0A09 dest = DPSnoo 0x005A0049 dest = pattern XOR dest */ static const struct OpTab { long winRasterOp; QPainter::CompositionMode qtRasterOp; } opTab[] = { // ### untested (conversion from Qt::RasterOp) { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP { 0x00EE0086, QPainter::RasterOp_SourceOrDestination }, // OrROP { 0x008800C6, QPainter::RasterOp_SourceAndDestination }, // AndROP { 0x00660046, QPainter::RasterOp_SourceXorDestination }, // XorROP // ---------------------------------------------------------------- // FIXME: Checked above this, below is still todo // ---------------------------------------------------------------- { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP }; int i; for (i = 0 ; i < 15 ; i++) if (opTab[i].winRasterOp == rop) break; if (i < 15) return opTab[i].qtRasterOp; else return QPainter::CompositionMode_Source; } } // xnamespace... diff --git a/libs/widgets/KoGlobal.cpp b/libs/widgets/KoGlobal.cpp index 2f971897a5..3b936e6c48 100644 --- a/libs/widgets/KoGlobal.cpp +++ b/libs/widgets/KoGlobal.cpp @@ -1,155 +1,158 @@ /* This file is part of the KDE project Copyright (C) 2001 David Faure Copyright 2003 Nicolas GOUTTE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoGlobal.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoGlobal, s_instance) KoGlobal* KoGlobal::self() { return s_instance; } KoGlobal::KoGlobal() : m_pointSize(-1) , m_calligraConfig(0) { // Fixes a bug where values from some config files are not picked up // due to KSharedConfig::openConfig() being initialized before paths have been set up above. // NOTE: Values set without a sync() call before KoGlobal has been initialized will not stick KSharedConfig::openConfig()->reparseConfiguration(); } KoGlobal::~KoGlobal() { delete m_calligraConfig; } QStringList KoGlobal::_listOfLanguageTags() { if (m_langMap.isEmpty()) createListOfLanguages(); return m_langMap.values(); } QStringList KoGlobal::_listOfLanguages() { if (m_langMap.empty()) createListOfLanguages(); return m_langMap.keys(); } void KoGlobal::createListOfLanguages() { KConfig config("all_languages", KConfig::NoGlobals); // Note that we could also use KLocale::allLanguagesTwoAlpha QMap seenLanguages; const QStringList langlist = config.groupList(); for (QStringList::ConstIterator itall = langlist.begin(); itall != langlist.end(); ++itall) { const QString tag = *itall; const QString name = config.group(tag).readEntry("Name", tag); // e.g. name is "French" and tag is "fr" // The QMap does the sorting on the display-name, so that // comboboxes are sorted. m_langMap.insert(name, tag); seenLanguages.insert(tag, true); } // Also take a look at the installed translations. // Many of them are already in all_languages but all_languages doesn't // currently have en_GB or en_US etc. - const QStringList translationList = KoResourcePaths::findAllResources("locale", - QString::fromLatin1("*/entry.desktop")); - for (QStringList::ConstIterator it = translationList.begin(); + QStringList translationList = KoResourcePaths::findAllResources("locale", + QString::fromLatin1("*/entry.desktop")) + + KoResourcePaths::findAllResources("locale", + QString::fromLatin1("*/kf5_entry.desktop"));; + translationList.removeDuplicates(); + for (QStringList::Iterator it = translationList.begin(); it != translationList.end(); ++it) { // Extract the language tag from the directory name QString tag = *it; int index = tag.lastIndexOf('/'); tag = tag.left(index); index = tag.lastIndexOf('/'); tag = tag.mid(index + 1); if (seenLanguages.find(tag) == seenLanguages.end()) { KConfig entry(*it, KConfig::SimpleConfig); const QString name = entry.group("KCM Locale").readEntry("Name", tag); // e.g. name is "US English" and tag is "en_US" m_langMap.insert(name, tag); // enable this if writing a third way of finding languages below //seenLanguages.insert( tag, true ); } } // #### We also might not have an entry for a language where spellchecking is supported, // but no KDE translation is available, like fr_CA. // How to add them? } QString KoGlobal::tagOfLanguage(const QString & _lang) { const LanguageMap& map = self()->m_langMap; QMap::ConstIterator it = map.find(_lang); if (it != map.end()) return *it; return QString(); } QString KoGlobal::languageFromTag(const QString &langTag) { const LanguageMap& map = self()->m_langMap; QMap::ConstIterator it = map.begin(); const QMap::ConstIterator end = map.end(); for (; it != end; ++it) if (it.value() == langTag) return it.key(); // Language code not found. Better return the code (tag) than nothing. return langTag; } KConfig* KoGlobal::_calligraConfig() { if (!m_calligraConfig) { m_calligraConfig = new KConfig("kritarc"); } return m_calligraConfig; } diff --git a/libs/widgets/KoResourceItemChooser.cpp b/libs/widgets/KoResourceItemChooser.cpp index 0894cc021f..86a1a71cd8 100644 --- a/libs/widgets/KoResourceItemChooser.cpp +++ b/libs/widgets/KoResourceItemChooser.cpp @@ -1,554 +1,609 @@ /* 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 "KoResourceItemChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceServerAdapter.h" #include "KoResourceItemView.h" #include "KoResourceItemDelegate.h" #include "KoResourceModel.h" #include #include "KoResourceTaggingManager.h" #include "KoTagFilterWidget.h" #include "KoTagChooserWidget.h" #include "KoResourceItemChooserSync.h" class Q_DECL_HIDDEN KoResourceItemChooser::Private { public: Private() : model(0) , view(0) , buttonGroup(0) , viewModeButton(0) , usePreview(false) , previewScroller(0) , previewLabel(0) , splitter(0) , tiledPreview(false) , grayscalePreview(false) , synced(false) , updatesBlocked(false) , savedResourceWhileReset(0) {} KoResourceModel *model; KoResourceTaggingManager *tagManager; KoResourceItemView *view; QButtonGroup *buttonGroup; QToolButton *viewModeButton; bool usePreview; QScrollArea *previewScroller; QLabel *previewLabel; QSplitter *splitter; QGridLayout *buttonLayout; bool tiledPreview; bool grayscalePreview; bool synced; bool updatesBlocked; KoResource *savedResourceWhileReset; QList customButtons; }; KoResourceItemChooser::KoResourceItemChooser(QSharedPointer resourceAdapter, QWidget *parent, bool usePreview) : QWidget(parent) , d(new Private()) { Q_ASSERT(resourceAdapter); d->splitter = new QSplitter(this); d->model = new KoResourceModel(resourceAdapter, this); connect(d->model, SIGNAL(beforeResourcesLayoutReset(KoResource *)), SLOT(slotBeforeResourcesLayoutReset(KoResource *))); connect(d->model, SIGNAL(afterResourcesLayoutReset()), SLOT(slotAfterResourcesLayoutReset())); d->view = new KoResourceItemView(this); d->view->setObjectName("ResourceItemview"); d->view->setModel(d->model); d->view->setItemDelegate(new KoResourceItemDelegate(this)); d->view->setSelectionMode(QAbstractItemView::SingleSelection); d->view->viewport()->installEventFilter(this); connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activated(QModelIndex))); connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex))); connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView())); d->splitter->addWidget(d->view); d->splitter->setStretchFactor(0, 2); d->usePreview = usePreview; if (d->usePreview) { d->previewScroller = new QScrollArea(this); d->previewScroller->setWidgetResizable(true); d->previewScroller->setBackgroundRole(QPalette::Dark); d->previewScroller->setVisible(true); d->previewScroller->setAlignment(Qt::AlignCenter); d->previewLabel = new QLabel(this); d->previewScroller->setWidget(d->previewLabel); d->splitter->addWidget(d->previewScroller); if (d->splitter->count() == 2) { d->splitter->setSizes(QList() << 280 << 160); } } d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(d->splitter, SIGNAL(splitterMoved(int, int)), SIGNAL(splitterMoved())); d->buttonGroup = new QButtonGroup(this); d->buttonGroup->setExclusive(false); QGridLayout *layout = new QGridLayout(this); d->buttonLayout = new QGridLayout(); QPushButton *button = new QPushButton(this); button->setIcon(koIcon("document-open")); button->setToolTip(i18nc("@info:tooltip", "Import resource")); button->setEnabled(true); d->buttonGroup->addButton(button, Button_Import); d->buttonLayout->addWidget(button, 0, 0); button = new QPushButton(this); button->setIcon(koIcon("trash-empty")); button->setToolTip(i18nc("@info:tooltip", "Delete resource")); button->setEnabled(false); d->buttonGroup->addButton(button, Button_Remove); d->buttonLayout->addWidget(button, 0, 1); connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int))); d->buttonLayout->setColumnStretch(0, 1); d->buttonLayout->setColumnStretch(1, 1); d->buttonLayout->setColumnStretch(2, 2); d->buttonLayout->setSpacing(0); d->buttonLayout->setMargin(0); d->viewModeButton = new QToolButton(this); d->viewModeButton->setIcon(koIcon("view-choose")); d->viewModeButton->setPopupMode(QToolButton::InstantPopup); d->viewModeButton->setVisible(false); d->tagManager = new KoResourceTaggingManager(d->model, this); connect(d->tagManager, SIGNAL(updateView()), this, SLOT(updateView())); layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0); layout->addWidget(d->viewModeButton, 0, 1); layout->addWidget(d->splitter, 1, 0, 1, 2); layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 2); layout->addLayout(d->buttonLayout, 3, 0, 1, 2); layout->setMargin(0); layout->setSpacing(0); updateButtonState(); showTaggingBar(false); activated(d->model->index(0, 0)); } KoResourceItemChooser::~KoResourceItemChooser() { disconnect(); delete d; } void KoResourceItemChooser::slotButtonClicked(int button) { if (button == Button_Import) { QString extensions = d->model->extensions(); QStringList mimeTypes; Q_FOREACH(const QString &suffix, extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->model->importResourceFile(filename); } else if (button == Button_Remove) { QModelIndex index = d->view->currentIndex(); int row = index.row(); int column = index.column(); if (index.isValid()) { KoResource *resource = resourceFromModelIndex(index); if (resource) { d->model->removeResource(resource); } } if (column == 0) { int rowMin = --row; row = qBound(0, rowMin, row); } int columnMin = --column; column = qBound(0, columnMin, column); setCurrentItem(row, column); activated(d->model->index(row, column)); } updateButtonState(); } void KoResourceItemChooser::showButtons(bool show) { foreach (QAbstractButton * button, d->buttonGroup->buttons()) { show ? button->show() : button->hide(); } Q_FOREACH (QAbstractButton *button, d->customButtons) { show ? button->show() : button->hide(); } } void KoResourceItemChooser::addCustomButton(QAbstractButton *button, int cell) { d->buttonLayout->addWidget(button, 0, cell); d->buttonLayout->setColumnStretch(2, 1); d->buttonLayout->setColumnStretch(3, 1); } void KoResourceItemChooser::showTaggingBar(bool show) { d->tagManager->showTaggingBar(show); } void KoResourceItemChooser::setRowCount(int rowCount) { int resourceCount = d->model->resourcesCount(); d->model->setColumnCount(static_cast(resourceCount) / rowCount); //Force an update to get the right row height (in theory) QRect geometry = d->view->geometry(); d->view->setViewMode(KoResourceItemView::FIXED_ROWS); d->view->setGeometry(geometry.adjusted(0, 0, 0, 1)); d->view->setGeometry(geometry); } void KoResourceItemChooser::setColumnCount(int columnCount) { d->model->setColumnCount(columnCount); } void KoResourceItemChooser::setRowHeight(int rowHeight) { d->view->verticalHeader()->setDefaultSectionSize(rowHeight); } void KoResourceItemChooser::setColumnWidth(int columnWidth) { d->view->horizontalHeader()->setDefaultSectionSize(columnWidth); } void KoResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate) { d->view->setItemDelegate(delegate); } KoResource *KoResourceItemChooser::currentResource() const { QModelIndex index = d->view->currentIndex(); if (index.isValid()) { return resourceFromModelIndex(index); } return 0; } void KoResourceItemChooser::setCurrentResource(KoResource *resource) { // don't update if the change came from the same chooser if (d->updatesBlocked) { return; } QModelIndex index = d->model->indexFromResource(resource); d->view->setCurrentIndex(index); updatePreview(index.isValid() ? resource : 0); } void KoResourceItemChooser::slotBeforeResourcesLayoutReset(KoResource *activateAfterReset) { d->savedResourceWhileReset = activateAfterReset ? activateAfterReset : currentResource(); } void KoResourceItemChooser::slotAfterResourcesLayoutReset() { if (d->savedResourceWhileReset) { this->blockSignals(true); setCurrentResource(d->savedResourceWhileReset); this->blockSignals(false); } } void KoResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation) { d->splitter->setOrientation(orientation); } void KoResourceItemChooser::setPreviewTiled(bool tiled) { d->tiledPreview = tiled; } void KoResourceItemChooser::setGrayscalePreview(bool grayscale) { d->grayscalePreview = grayscale; } void KoResourceItemChooser::setCurrentItem(int row, int column) { QModelIndex index = d->model->index(row, column); if (!index.isValid()) return; d->view->setCurrentIndex(index); if (index.isValid()) { updatePreview(resourceFromModelIndex(index)); } } void KoResourceItemChooser::setProxyModel(QAbstractProxyModel *proxyModel) { proxyModel->setSourceModel(d->model); d->view->setModel(proxyModel); } void KoResourceItemChooser::activated(const QModelIndex &/*index*/) { KoResource *resource = currentResource(); if (resource) { d->updatesBlocked = true; emit resourceSelected(resource); d->updatesBlocked = false; updatePreview(resource); updateButtonState(); } } void KoResourceItemChooser::clicked(const QModelIndex &index) { Q_UNUSED(index); KoResource *resource = currentResource(); if (resource) { emit resourceClicked(resource); } } void KoResourceItemChooser::updateButtonState() { QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove); if (! removeButton) return; KoResource *resource = currentResource(); if (resource) { removeButton->setEnabled(!resource->permanent()); return; } removeButton->setEnabled(false); } void KoResourceItemChooser::updatePreview(KoResource *resource) { if (!d->usePreview) return; if (!resource) { d->previewLabel->setPixmap(QPixmap()); return; } QImage image = resource->image(); if (image.format() != QImage::Format_RGB32 || image.format() != QImage::Format_ARGB32 || image.format() != QImage::Format_ARGB32_Premultiplied) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); } if (d->tiledPreview) { int width = d->previewScroller->width() * 4; int height = d->previewScroller->height() * 4; QImage img(width, height, image.format()); QPainter gc(&img); gc.fillRect(img.rect(), Qt::white); gc.setPen(Qt::NoPen); gc.setBrush(QBrush(image)); gc.drawRect(img.rect()); image = img; } // Only convert to grayscale if it is rgb. Otherwise, it's gray already. if (d->grayscalePreview && !image.isGrayscale()) { QRgb *pixel = reinterpret_cast(image.bits()); for (int row = 0; row < image.height(); ++row) { for (int col = 0; col < image.width(); ++col) { const QRgb currentPixel = pixel[row * image.width() + col]; const int red = qRed(currentPixel); const int green = qGreen(currentPixel); const int blue = qBlue(currentPixel); const int grayValue = (red * 11 + green * 16 + blue * 5) / 32; pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue); } } } d->previewLabel->setPixmap(QPixmap::fromImage(image)); } KoResource *KoResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const { if (!index.isValid()) return 0; const QAbstractProxyModel *proxyModel = dynamic_cast(index.model()); if (proxyModel) { //Get original model index, because proxy models destroy the internalPointer QModelIndex originalIndex = proxyModel->mapToSource(index); return static_cast(originalIndex.internalPointer()); } return static_cast(index.internalPointer()); } QSize KoResourceItemChooser::viewSize() const { return d->view->size(); } KoResourceItemView *KoResourceItemChooser::itemView() const { return d->view; } void KoResourceItemChooser::contextMenuRequested(const QPoint &pos) { d->tagManager->contextMenuRequested(currentResource(), pos); } void KoResourceItemChooser::setViewModeButtonVisible(bool visible) { d->viewModeButton->setVisible(visible); } QToolButton *KoResourceItemChooser::viewModeButton() const { return d->viewModeButton; } void KoResourceItemChooser::setSynced(bool sync) { if (d->synced == sync) return; d->synced = sync; KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance(); if (sync) { connect(chooserSync, SIGNAL(baseLenghtChanged(int)), SLOT(baseLengthChanged(int))); baseLengthChanged(chooserSync->baseLength()); } else { chooserSync->disconnect(this); } } void KoResourceItemChooser::baseLengthChanged(int length) { if (d->synced) { int resourceCount = d->model->resourcesCount(); int width = d->view->width(); int maxColums = width / length; int cols = width / (2 * length) + 1; while (cols <= maxColums) { int size = width / cols; int rows = ceil(resourceCount / (double)cols); if (rows * size < (d->view->height() - 5)) { break; } cols++; } setColumnCount(cols); } d->view->updateView(); } bool KoResourceItemChooser::eventFilter(QObject *object, QEvent *event) { if (d->synced && event->type() == QEvent::Wheel) { KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance(); QWheelEvent *qwheel = static_cast(event); if (qwheel->modifiers() & Qt::ControlModifier) { int degrees = qwheel->delta() / 8; int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10; chooserSync->setBaseLength(newBaseLength); return true; } } return QObject::eventFilter(object, event); } +void KoResourceItemChooser::configureKineticScrolling(int gesture, int sensitivity, bool scrollbar) +{ + QScroller::ScrollerGestureType gestureType; + + switch (gesture) { + case 1: { + gestureType = QScroller::TouchGesture; + break; + } + case 2: { + gestureType = QScroller::LeftMouseButtonGesture; + break; + } + default: + return; + } + + KoResourceItemView *view = itemView(); + + view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + if (!scrollbar) { + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + + QScroller *scroller = QScroller::scroller(view); + scroller->grabGesture(view, gestureType); + + QScrollerProperties sp; + + // DragStartDistance seems to be based on meter per second; though it's + // not explicitly documented, other QScroller values are in that metric. + + // To start kinetic scrolling, with minimal sensitity, we expect a drag + // of 10 mm, with minimum sensitity any > 0 mm. + + const float mm = 0.001f; // 1 millimeter + const float resistance = 1.0f - (sensitivity / 100.0f); + + sp.setScrollMetric(QScrollerProperties::DragStartDistance, resistance * 10.0f * mm); + sp.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor, 1.0f); + sp.setScrollMetric(QScrollerProperties::MinimumVelocity, 0.0f); + sp.setScrollMetric(QScrollerProperties::AxisLockThreshold, 1.0f); + sp.setScrollMetric(QScrollerProperties::MaximumClickThroughVelocity, 0.0f); + sp.setScrollMetric(QScrollerProperties::MousePressEventDelay, 1.0f - 0.75f * resistance); + sp.setScrollMetric(QScrollerProperties::AcceleratingFlickSpeedupFactor, 1.5f); + + sp.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOn); + sp.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, 0.1); + sp.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, 0.3); + sp.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.1); + sp.setScrollMetric(QScrollerProperties::OvershootScrollTime, 0.4); + + scroller->setScrollerProperties(sp); +} + void KoResourceItemChooser::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateView(); } void KoResourceItemChooser::showEvent(QShowEvent *event) { QWidget::showEvent(event); updateView(); } void KoResourceItemChooser::updateView() { if (d->synced) { KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance(); baseLengthChanged(chooserSync->baseLength()); } } diff --git a/libs/widgets/KoResourceItemChooser.h b/libs/widgets/KoResourceItemChooser.h index 930f974056..2b38e7b4c4 100644 --- a/libs/widgets/KoResourceItemChooser.h +++ b/libs/widgets/KoResourceItemChooser.h @@ -1,153 +1,157 @@ /* 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) 2010 Boudewijn Rempt 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. */ #ifndef KO_RESOURCE_ITEM_CHOOSER #define KO_RESOURCE_ITEM_CHOOSER #include +#include #include "kritawidgets_export.h" class QModelIndex; class QAbstractProxyModel; class QAbstractItemDelegate; class QAbstractButton; class QToolButton; class KoAbstractResourceServerAdapter; class KoResourceItemView; class KoResource; /** * A widget that contains a KoResourceChooser as well * as an import/export button */ class KRITAWIDGETS_EXPORT KoResourceItemChooser : public QWidget { Q_OBJECT public: enum Buttons { Button_Import, Button_Remove }; /// \p usePreview shows the aside preview with the resource's image explicit KoResourceItemChooser(QSharedPointer resourceAdapter, QWidget *parent = 0, bool usePreview = false); ~KoResourceItemChooser() override; /// Sets number of columns in the view and causes the number of rows to be calculated accordingly void setColumnCount(int columnCount); /// Sets number of rows in the view and causes the number of columns to be calculated accordingly void setRowCount(int rowCount); /// Sets the height of the view rows void setRowHeight(int rowHeight); /// Sets the width of the view columns void setColumnWidth(int columnWidth); /// Sets a custom delegate for the view void setItemDelegate(QAbstractItemDelegate *delegate); /// Gets the currently selected resource /// @returns the selected resource, 0 is no resource is selected KoResource *currentResource() const; /// Sets the item representing the resource as selected void setCurrentResource(KoResource *resource); /** * Sets the selected resource, does nothing if there is no valid item * @param row row of the item * @param column column of the item */ void setCurrentItem(int row, int column); void showButtons(bool show); void addCustomButton(QAbstractButton *button, int cell); /// determines whether the preview right or below the splitter void setPreviewOrientation(Qt::Orientation orientation); /// determines whether the preview should tile the resource's image or not void setPreviewTiled(bool tiled); /// shows the preview converted to grayscale void setGrayscalePreview(bool grayscale); /// sets the visibilty of tagging KlineEdits. void showTaggingBar(bool show); ///Set a proxy model with will be used to filter the resources void setProxyModel(QAbstractProxyModel *proxyModel); QSize viewSize() const; KoResourceItemView *itemView() const; void setViewModeButtonVisible(bool visible); QToolButton *viewModeButton() const; void setSynced(bool sync); bool eventFilter(QObject *object, QEvent *event) override; + /// sets up this chooser for kinetic (drag triggered) scrolling + void configureKineticScrolling(int gesture, int sensitivity, bool scrollbar); + Q_SIGNALS: /// Emitted when a resource was selected void resourceSelected(KoResource *resource); /// Emitted when an *already selected* resource is clicked /// again void resourceClicked(KoResource *resource); void splitterMoved(); public Q_SLOTS: void slotButtonClicked(int button); private Q_SLOTS: void activated(const QModelIndex &index); void clicked(const QModelIndex &index); void contextMenuRequested(const QPoint &pos); void baseLengthChanged(int length); void slotBeforeResourcesLayoutReset(KoResource *activateAfterReset); void slotAfterResourcesLayoutReset(); void updateView(); protected: void showEvent(QShowEvent *event) override; private: void updateButtonState(); void updatePreview(KoResource *resource); void resizeEvent(QResizeEvent *event) override; /// Resource for a given model index /// @returns the resource pointer, 0 is index not valid KoResource *resourceFromModelIndex(const QModelIndex &index) const; class Private; Private *const d; }; #endif // KO_RESOURCE_ITEM_CHOOSER diff --git a/packaging/linux/appimage/build-release.sh b/packaging/linux/appimage/build-release.sh old mode 100644 new mode 100755 index a8e8b8e159..8ade17ae8e --- a/packaging/linux/appimage/build-release.sh +++ b/packaging/linux/appimage/build-release.sh @@ -1,250 +1,282 @@ #!/bin/bash -RELEASE=3.1.2.0 +RELEASE=3.3.2.1 # Enter a CentOS 6 chroot (you could use other methods) # git clone https://github.com/probonopd/AppImageKit.git # ./AppImageKit/build.sh # sudo ./AppImageKit/AppImageAssistant.AppDir/testappimage /isodevice/boot/iso/CentOS-6.5-x86_64-LiveCD.iso bash # Halt on errors set -e # Be verbose set -x # Now we are inside CentOS 6 grep -r "CentOS release 6" /etc/redhat-release || exit 1 # If we are running inside Travis CI, then we want to build Krita # and we remove the $DO_NOT_BUILD_KRITA environment variable # that was used in the process of generating the Docker image. # Also we do not want to download and build the dependencies every # time we build Krita on travis in the docker image. In order to # use newer dependencies, we re-build the docker image instead. #unset DO_NOT_BUILD_KRITA #NO_DOWNLOAD=1 # clean up rm -rf /out/* rm -rf /krita.appdir rm -rf /krita_build +rm -rf /gmic-qt-build +mkdir gmic-qt-build mkdir /krita_build # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. That's # not always set correctly in CentOS 6.7 export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # Determine which architecture should be built if [[ "$(arch)" = "i686" || "$(arch)" = "x86_64" ]] ; then ARCH=$(arch) else echo "Architecture could not be determined" exit 1 fi # if the library path doesn't point to our usr/lib, linking will be broken and we won't find all deps either export LD_LIBRARY_PATH=/usr/lib64/:/usr/lib:/krita.appdir/usr/lib cd / # Prepare the install location rm -rf /krita.appdir/ || true mkdir -p /krita.appdir/usr/bin # make sure lib and lib64 are the same thing mkdir -p /krita.appdir/usr/lib cd /krita.appdir/usr ln -s lib lib64 # Use the new compiler . /opt/rh/devtoolset-3/enable +cd / + +git_pull_rebase_helper() +{ + git reset --hard HEAD + git pull + +} +# fetch and build gmic +if [ ! -d /gmic ] ; then + git clone --depth 1 https://github.com/dtschump/gmic.git +fi + +cd /gmic/ +git_pull_rebase_helper +cd / +make -C gmic/src CImg.h gmic_stdlib.h + +# fetch and build gmic-qt +if [ ! -d /gmic-qt ] ; then + git clone --depth 1 https://github.com/c-koi/gmic-qt.git +fi + +cd /gmic-qt/ +git_pull_rebase_helper + +cd /gmic-qt-build +cmake3 ../gmic-qt -DGMIC_QT_HOST=krita -DCMAKE_BUILD_TYPE=Release +make -j4 +cp gmic_krita_qt /krita.appdir/usr/bin + # fetch and build krita cd / -#wget http://files.kde.org/krita/krita-$RELEASE.tar.gz -#wget http://www.valdyas.org/~boud/krita-$RELEASE.tar.gz -wget http://download.kde.org/unstable/krita/$RELEASE/krita-$RELEASE.tar.gz -tar -xf krita-$RELEASE.tar.gz +tar -xf krita-$RELEASE.tar.xz cd /krita_build cmake3 ../krita-$RELEASE \ -DCMAKE_INSTALL_PREFIX:PATH=/krita.appdir/usr \ -DDEFINE_NO_DEPRECATED=1 \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DPACKAGERS_BUILD=1 \ -DBUILD_TESTING=FALSE \ -DKDE4_BUILD_TESTS=FALSE \ -DHAVE_MEMORY_LEAK_TRACKER=FALSE make -j4 install cd /krita.appdir # FIXME: How to find out which subset of plugins is really needed? I used strace when running the binary cp -r /usr/plugins ./usr/bin/ # copy the Qt translation cp -r /usr/translations ./usr cp $(ldconfig -p | grep libsasl2.so.2 | cut -d ">" -f 2 | xargs) ./usr/lib/ cp $(ldconfig -p | grep libGL.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # otherwise segfaults!? cp $(ldconfig -p | grep libGLU.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # otherwise segfaults!? # Fedora 23 seemed to be missing SOMETHING from the Centos 6.7. The only message was: # This application failed to start because it could not find or load the Qt platform plugin "xcb". # Setting export QT_DEBUG_PLUGINS=1 revealed the cause. # QLibraryPrivate::loadPlugin failed on "/usr/lib64/qt5/plugins/platforms/libqxcb.so" : # "Cannot load library /usr/lib64/qt5/plugins/platforms/libqxcb.so: (/lib64/libEGL.so.1: undefined symbol: drmGetNodeTypeFromFd)" # Which means that we have to copy libEGL.so.1 in too cp $(ldconfig -p | grep libEGL.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # Otherwise F23 cannot load the Qt platform plugin "xcb" # let's not copy xcb itself, that breaks on dri3 systems https://bugs.kde.org/show_bug.cgi?id=360552 #cp $(ldconfig -p | grep libxcb.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ cp $(ldconfig -p | grep libfreetype.so.6 | cut -d ">" -f 2 | xargs) ./usr/lib/ # For Fedora 20 + ldd usr/bin/krita | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true +ldd usr/bin/gmic_krita_qt | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true #ldd usr/lib64/krita/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true #ldd usr/lib64/plugins/imageformats/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true ldd usr/bin/plugins/platforms/libqxcb.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true # Copy in the indirect dependencies FILES=$(find . -type f -executable) for FILE in $FILES ; do ldd "${FILE}" | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' usr/lib || true done #DEPS="" #for FILE in $FILES ; do # ldd "${FILE}" | grep "=>" | awk '{print $3}' | xargs -I '{}' echo '{}' > DEPSFILE #done #DEPS=$(cat DEPSFILE |sort | uniq) #for FILE in $DEPS ; do # if [ -f $FILE ] ; then # echo $FILE # cp --parents -rfL $FILE ./ # fi #done #rm -f DEPSFILE # The following are assumed to be part of the base system rm -f usr/lib/libcom_err.so.2 || true rm -f usr/lib/libcrypt.so.1 || true rm -f usr/lib/libdl.so.2 || true rm -f usr/lib/libexpat.so.1 || true #rm -f usr/lib/libfontconfig.so.1 || true rm -f usr/lib/libgcc_s.so.1 || true rm -f usr/lib/libglib-2.0.so.0 || true rm -f usr/lib/libgpg-error.so.0 || true rm -f usr/lib/libgssapi_krb5.so.2 || true rm -f usr/lib/libgssapi.so.3 || true rm -f usr/lib/libhcrypto.so.4 || true rm -f usr/lib/libheimbase.so.1 || true rm -f usr/lib/libheimntlm.so.0 || true rm -f usr/lib/libhx509.so.5 || true rm -f usr/lib/libICE.so.6 || true rm -f usr/lib/libidn.so.11 || true rm -f usr/lib/libk5crypto.so.3 || true rm -f usr/lib/libkeyutils.so.1 || true rm -f usr/lib/libkrb5.so.26 || true rm -f usr/lib/libkrb5.so.3 || true rm -f usr/lib/libkrb5support.so.0 || true # rm -f usr/lib/liblber-2.4.so.2 || true # needed for debian wheezy # rm -f usr/lib/libldap_r-2.4.so.2 || true # needed for debian wheezy rm -f usr/lib/libm.so.6 || true rm -f usr/lib/libp11-kit.so.0 || true rm -f usr/lib/libpcre.so.3 || true rm -f usr/lib/libpthread.so.0 || true rm -f usr/lib/libresolv.so.2 || true rm -f usr/lib/libroken.so.18 || true rm -f usr/lib/librt.so.1 || true -rm -f usr/lib/libsasl2.so.2 || true +#rm -f usr/lib/libsasl2.so.2 || true rm -f usr/lib/libSM.so.6 || true rm -f usr/lib/libusb-1.0.so.0 || true rm -f usr/lib/libuuid.so.1 || true rm -f usr/lib/libwind.so.0 || true rm -f usr/lib/libfontconfig.so.* || true # Remove these libraries, we need to use the system versions; this means 11.04 is not supported (12.04 is our baseline) rm -f usr/lib/libGL.so.* || true rm -f usr/lib/libdrm.so.* || true rm -f usr/lib/libX11.so.* || true #rm -f usr/lib/libz.so.1 || true # These seem to be available on most systems but not Ubuntu 11.04 # rm -f usr/lib/libffi.so.6 usr/lib/libGL.so.1 usr/lib/libglapi.so.0 usr/lib/libxcb.so.1 usr/lib/libxcb-glx.so.0 || true # Delete potentially dangerous libraries rm -f usr/lib/libstdc* usr/lib/libgobject* usr/lib/libc.so.* || true rm -f usr/lib/libxcb.so.1 # Do NOT delete libX* because otherwise on Ubuntu 11.04: # loaded library "Xcursor" malloc.c:3096: sYSMALLOc: Assertion (...) Aborted # We don't bundle the developer stuff rm -rf usr/include || true rm -rf usr/lib/cmake3 || true rm -rf usr/lib/pkgconfig || true rm -rf usr/share/ECM/ || true rm -rf usr/share/gettext || true rm -rf usr/share/pkgconfig || true mv usr/share/locale usr/share/krita || true strip usr/lib/kritaplugins/* usr/bin/* usr/lib/* || true # Since we set /krita.appdir as the prefix, we need to patch it away too (FIXME) # Probably it would be better to use /app as a prefix because it has the same length for all apps cd usr/ ; find . -type f -exec sed -i -e 's|/krita.appdir/usr/|./././././././././|g' {} \; ; cd .. # On openSUSE Qt is picking up the wrong libqxcb.so # (the one from the system when in fact it should use the bundled one) - is this a Qt bug? # Also, Krita has a hardcoded /usr which we patch away cd usr/ ; find . -type f -exec sed -i -e 's|/usr|././|g' {} \; ; cd .. # We do not bundle this, so let's not search that inside the AppImage. # Fixes "Qt: Failed to create XKB context!" and lets us enter text sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/bin/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/lib/libQt5XcbQpa.so.5 # Workaround for: # D-Bus library appears to be incorrectly set up; # failed to read machine uuid: Failed to open # The file is more commonly in /etc/machine-id # sed -i -e 's|/var/lib/dbus/machine-id|//././././etc/machine-id|g' ./usr/lib/libdbus-1.so.3 # or rm -f ./usr/lib/libdbus-1.so.3 || true cp ../AppImageKit/AppRun . cp ./usr/share/applications/org.kde.krita.desktop krita.desktop cp /krita/krita/pics/app/64-apps-calligrakrita.png calligrakrita.png # replace krita with the lib-checking startup script. #cd /krita.appdir/usr/bin #mv krita krita.real #wget https://raw.githubusercontent.com/boudewijnrempt/AppImages/master/recipes/krita/krita #chmod a+rx krita cd / APP=krita cd / VER=$(grep "#define KRITA_VERSION_STRING" krita_build/libs/version/kritaversion.h | cut -d '"' -f 2) VERSION=$VER VERSION="$(sed s/\ /-/g <<<$VERSION)" echo $VERSION if [[ "$ARCH" = "x86_64" ]] ; then APPIMAGE=$APP"-"$VERSION"-x86_64.appimage" fi if [[ "$ARCH" = "i686" ]] ; then APPIMAGE=$APP"-"$VERSION"-i386.appimage" fi echo $APPIMAGE mkdir -p /out rm -f /out/*.AppImage || true AppImageKit/AppImageAssistant.AppDir/package /krita.appdir/ /out/$APPIMAGE chmod a+rwx /out/$APPIMAGE # So that we can edit the AppImage outside of the Docker container diff --git a/packaging/windows/package-complete.cmd b/packaging/windows/package-complete.cmd new file mode 100644 index 0000000000..25b1d50b29 --- /dev/null +++ b/packaging/windows/package-complete.cmd @@ -0,0 +1,724 @@ +@echo off +:: This batch script is meant to prepare a Krita package folder to be zipped or +:: to be a base for the installer. + +:: -------- + +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 --package-name ^] [ 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 --package-name ^ Specify the package name +echo. +echo Path options: +echo --src-dir ^ Specify Krita source dir +echo If unspecified, this will be determined from +echo the script location +echo --deps-install-dir ^ Specify deps install dir +echo --krita-install-dir ^ Specify Krita install dir +echo. +goto :EOF +:usage_and_exit +call :usage +exit /b +:usage_and_fail +call :usage +exit /b 100 + + +:: ---------------------------- +:begin + +echo Krita Windows packaging script +echo. + + +:: command-line args parsing +set ARG_NO_INTERACTIVE= +set ARG_PACKAGE_NAME= +set ARG_SRC_DIR= +set ARG_DEPS_INSTALL_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" == "--package-name" ( + if not "%ARG_PACKAGE_NAME%" == "" ( + echo ERROR: Arg --package-name specified more than once 1>&2 + echo. + goto usage_and_fail + ) + if "%~2" == "" ( + echo ERROR: Arg --package-name is empty 1>&2 + echo. + goto usage_and_fail + ) + set "ARG_PACKAGE_NAME=%~2" + shift /2 + 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" == "--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-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" ( + if "%ARG_PACKAGE_NAME%" == "" ( + echo ERROR: Required arg --package-name not specified1>&2 + echo. + goto usage_and_fail + ) +) + +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_PACKAGE_NAME%" == "" ( + call :prompt_for_string ARG_PACKAGE_NAME "Provide package name" + if "ARG_PACKAGE_NAME" == "" ( + echo ERROR: Package name not set! 1>&2 + exit /b 102 + ) +) + + +:: Check environment config + +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! 1>&2 + exit /b 102 + ) + ) +) +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% + +:: Windows SDK is needed for windeployqt to get d3dcompiler_xx.dll +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 +) +if not "%HAVE_FXC_EXE%" == "1" ( + set WindowsSdkDir= + echo Windows SDK 10 with fxc.exe not found + echo If Qt was built with ANGLE ^(dynamic OpenGL^) support, the package might not work properly on some systems! +) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% + +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:~-19!" == "\packaging\windows\" ( + if exist "!_temp:~0,-19!\CMakeLists.txt" ( + if exist "!_temp:~0,-19!\3rdparty\CMakeLists.txt" ( + set "KRITA_SRC_DIR=!_temp:~0,-19!\" + 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 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 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% + + +:: Simple checking +if not exist "%DEPS_INSTALL_DIR%\" ( + echo ERROR: Cannot find the deps install folder! 1>&2 + exit /B 1 +) +if not exist "%KRITA_INSTALL_DIR%\" ( + echo ERROR: Cannot find the krita install folder! 1>&2 + exit /B 1 +) +if not "%DEPS_INSTALL_DIR: =%" == "%DEPS_INSTALL_DIR%" ( + echo ERROR: Deps install path contains space, which will not work properly! 1>&2 + exit /B 1 +) +if not "%KRITA_INSTALL_DIR: =%" == "%KRITA_INSTALL_DIR%" ( + echo ERROR: Krita install path contains space, which will not work properly! 1>&2 + exit /B 1 +) + +set pkg_name=%ARG_PACKAGE_NAME% +echo Package name is "%pkg_name%" + +set pkg_root=%CD%\%pkg_name% +echo Packaging dir is %pkg_root% +echo. +if exist %pkg_root% ( + echo ERROR: Packaging dir already exists! Please remove or rename it first. + exit /B 1 +) +if exist %pkg_root%.zip ( + echo ERROR: Packaging zip already exists! Please remove or rename it first. + exit /B 1 +) +if exist %pkg_root%-dbg.zip ( + echo ERROR: Packaging debug zip already exists! Please remove or rename it first. + exit /B 1 +) + +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%;%DEPS_INSTALL_DIR%\bin;%PATH% + + +echo. +echo Trying to guess GCC version... +g++ --version > NUL +if errorlevel 1 ( + echo ERROR: g++ is not working. + exit /B 1 +) +for /f "delims=" %%a in ('g++ --version ^| find "g++"') do set GCC_VERSION_LINE=%%a +echo -- %GCC_VERSION_LINE% +if "%GCC_VERSION_LINE:tdm64=%" == "%GCC_VERSION_LINE%" ( + echo Compiler doesn't look like TDM64-GCC, assuming simple mingw-w64 + set IS_TDM= +) else ( + echo Compiler looks like TDM64-GCC + set IS_TDM=1 +) + +echo. +echo Trying to guess target architecture... +objdump --version > NUL +if errorlevel 1 ( + echo ERROR: objdump is not working. + exit /B 1 +) +for /f "delims=, tokens=1" %%a in ('objdump -f %KRITA_INSTALL_DIR%\bin\krita.exe ^| find "architecture"') do set TARGET_ARCH_LINE=%%a +echo -- %TARGET_ARCH_LINE% +if "%TARGET_ARCH_LINE:x86-64=%" == "%TARGET_ARCH_LINE%" ( + echo Target looks like x86 + set IS_X64= +) else ( + echo Target looks like x64 + set IS_x64=1 +) + +echo. +echo Testing for objcopy... +objcopy --version > NUL +if errorlevel 1 ( + echo ERROR: objcopy is not working. + exit /B 1 +) + +echo. +echo Testing for strip... +strip --version > NUL +if errorlevel 1 ( + echo ERROR: strip is not working. + exit /B 1 +) + +echo. +echo Creating base directories... +mkdir %pkg_root% && ^ +mkdir %pkg_root%\bin && ^ +mkdir %pkg_root%\lib && ^ +mkdir %pkg_root%\share +if errorlevel 1 ( + echo ERROR: Cannot create packaging dir tree! + %PAUSE% + exit /B 1 +) + +echo. +echo Copying GCC libraries... +if x%IS_TDM% == x ( + if x%is_x64% == x ( + :: mingw-w64 x86 + set "STDLIBS=gcc_s_dw2-1 gomp-1 stdc++-6 winpthread-1" + ) else ( + :: mingw-w64 x64 + set "STDLIBS=gcc_s_seh-1 gomp-1 stdc++-6 winpthread-1" + ) +) else ( + if x%is_x64% == x ( + :: TDM-GCC x86 + set "STDLIBS=gomp-1" + ) else ( + :: TDM-GCC x64 + set "STDLIBS=gomp_64-1" + ) +) +for %%L in (%STDLIBS%) do copy "%MINGW_BIN_DIR%\lib%%L.dll" %pkg_root%\bin + +echo. +echo Copying files... +:: krita.exe +copy %KRITA_INSTALL_DIR%\bin\krita.exe %pkg_root%\bin +:: DLLs from bin/ +echo INFO: Copying all DLLs except Qt5* from bin/ +setlocal enableextensions enabledelayedexpansion +for /f "delims=" %%F in ('dir /b "%KRITA_INSTALL_DIR%\bin\*.dll"') do ( + set file=%%F + set file=!file:~0,3! + if not x!file! == xQt5 copy %KRITA_INSTALL_DIR%\bin\%%F %pkg_root%\bin +) +for /f "delims=" %%F in ('dir /b "%DEPS_INSTALL_DIR%\bin\*.dll"') do ( + set file=%%F + set file=!file:~0,3! + if not x!file! == xQt5 copy %DEPS_INSTALL_DIR%\bin\%%F %pkg_root%\bin +) +endlocal +:: symsrv.yes for Dr. Mingw +copy %DEPS_INSTALL_DIR%\bin\symsrv.yes %pkg_root%\bin +:: DLLs from lib/ +echo INFO: Copying all DLLs from lib/ (deps) +copy %DEPS_INSTALL_DIR%\lib\*.dll %pkg_root%\bin +:: Boost, there might be more than one leftover but we can't really do much +copy %DEPS_INSTALL_DIR%\bin\libboost_system-*.dll %pkg_root%\bin +:: KF5 plugins may be placed at different locations depending on how Qt is built +xcopy /S /Y /I %DEPS_INSTALL_DIR%\lib\plugins\imageformats %pkg_root%\bin\imageformats +xcopy /S /Y /I %DEPS_INSTALL_DIR%\plugins\imageformats %pkg_root%\bin\imageformats +xcopy /S /Y /I %DEPS_INSTALL_DIR%\lib\plugins\kf5 %pkg_root%\bin\kf5 +xcopy /S /Y /I %DEPS_INSTALL_DIR%\plugins\kf5 %pkg_root%\bin\kf5 +:: Qt Translations +:: it seems that windeployqt does these, but only *some* of these??? +mkdir %pkg_root%\bin\translations +setlocal enableextensions enabledelayedexpansion +for /f "delims=" %%F in ('dir /b "%DEPS_INSTALL_DIR%\translations\qt_*.qm"') do ( + :: Exclude qt_help_*.qm + set temp=%%F + set temp2=!temp:_help=! + if x!temp2! == x!temp! copy %DEPS_INSTALL_DIR%\translations\!temp! %pkg_root%\bin\translations\!temp! +) +endlocal +:: Krita plugins +xcopy /Y %KRITA_INSTALL_DIR%\lib\kritaplugins\*.dll %pkg_root%\lib\kritaplugins\ + +:: Share +xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\color %pkg_root%\share\color +xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\color-schemes %pkg_root%\share\color-schemes +xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\icons %pkg_root%\share\icons +xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\krita %pkg_root%\share\krita +xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\kritaplugins %pkg_root%\share\kritaplugins +xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\kf5 %pkg_root%\share\kf5 +xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\mime %pkg_root%\share\mime +:: Python libs +xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\krita\pykrita %pkg_root%\share\krita\pykrita +:: Not useful on Windows it seems +rem xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\appdata %pkg_root%\share\appdata +rem xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\applications %pkg_root%\share\applications +rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\doc %pkg_root%\share\doc +rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\kservices5 %pkg_root%\share\kservices5 +rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\man %pkg_root%\share\man +rem xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\ocio %pkg_root%\share\ocio + +:: Copy locale to bin +xcopy /Y /S /I %KRITA_INSTALL_DIR%\share\locale %pkg_root%\bin\locale +xcopy /Y /S /I %DEPS_INSTALL_DIR%\share\locale %pkg_root%\bin\locale + +:: Copy shortcut link from source (can't create it dynamically) +copy %KRITA_SRC_DIR%\packaging\windows\krita.lnk %pkg_root% + +set "QMLDIR_ARGS=--qmldir %DEPS_INSTALL_DIR%\qml" +if exist "%KRITA_INSTALL_DIR%\lib\qml" ( + xcopy /Y /S /I %KRITA_INSTALL_DIR%\lib\qml %pkg_root%\bin + :: This doesn't really seem to do anything + set "QMLDIR_ARGS=%QMLDIR_ARGS% --qmldir %KRITA_INSTALL_DIR%\lib\qml" +) + +if EXIST "%DEPS_INSTALL_DIR%\bin\gmic_krita_qt.exe" ( + copy %DEPS_INSTALL_DIR%\bin\gmic_krita_qt.exe %pkg_root%\bin + set "WINDEPLOYQT_GMIC_ARGS=%pkg_root%\bin\gmic_krita_qt.exe" +) + +:: windeployqt +windeployqt.exe %QMLDIR_ARGS% --release -gui -core -concurrent -network -printsupport -svg -xml -multimedia -qml -quick -quickwidgets %pkg_root%\bin\krita.exe %WINDEPLOYQT_GMIC_ARGS% +if errorlevel 1 ( + echo ERROR: WinDeployQt failed! 1>&2 + exit /B 1 +) + + +:: Copy embedded Python +xcopy /Y /S /I %DEPS_INSTALL_DIR%\python %pkg_root%\python + +:: For chopping relative path +:: 512 should be enough +:: n+2 to also account for a trailing backslash +setlocal enableextensions enabledelayedexpansion +for /L %%n in (1 1 512) do if "!pkg_root:~%%n,1!" neq "" set /a "pkg_root_len_plus_one=%%n+2" +endlocal & set pkg_root_len_plus_one=%pkg_root_len_plus_one% + +echo. +setlocal enableextensions enabledelayedexpansion +:: Remove Python cache files +for /d /r "%pkg_root%" %%F in (__pycache__\) do ( + if EXIST "%%F" ( + set relpath=%%F + set relpath=!relpath:~%pkg_root_len_plus_one%! + echo Deleting Python cache !relpath! + rmdir /S /Q "%%F" + ) +) +endlocal + +echo. +echo Splitting debug info from binaries... +call :split-debug "%pkg_root%\bin\krita.exe" bin\krita.exe +setlocal enableextensions enabledelayedexpansion +:: Find all DLLs +for /r "%pkg_root%" %%F in (*.dll) do ( + set relpath=%%F + set relpath=!relpath:~%pkg_root_len_plus_one%! + call :split-debug "%%F" !relpath! +) +endlocal +setlocal enableextensions enabledelayedexpansion +:: Find all Python native modules +for /r "%pkg_root%\share\krita\pykrita\" %%F in (*.pyd) do ( + set relpath=%%F + set relpath=!relpath:~%pkg_root_len_plus_one%! + call :split-debug "%%F" !relpath! +) +endlocal + +echo. +echo Packaging stripped binaries... +"%SEVENZIP_EXE%" a -tzip %pkg_name%.zip %pkg_root%\ "-xr^!.debug" +echo -------- + +echo. +echo Packaging debug info... +:: (note that the top-level package dir is not included) +"%SEVENZIP_EXE%" a -tzip %pkg_name%-dbg.zip -r %pkg_root%\*.debug +echo -------- + +echo. +echo. +echo Krita packaged as %pkg_name%.zip +if exist %pkg_name%-dbg.zip echo Debug info packaged as %pkg_name%-dbg.zip +echo Packaging dir is %pkg_root% +echo NOTE: Do not create installer with packaging dir. Extract from +echo %pkg_name%.zip instead, +echo and do _not_ run krita inside the extracted directory because it will +echo create extra unnecessary files. +echo. +echo Please remember to actually test the package before releasing it. +echo. +%PAUSE% + +exit /b + +:split-debug +echo Splitting debug info of %2 +objcopy --only-keep-debug %~1 %~1.debug +if ERRORLEVEL 1 exit /b %ERRORLEVEL% +:: If the debug file is small enough then consider there being no debug info. +:: Discard these files since they somehow make gdb crash. +call :getfilesize %~1.debug +if /i %getfilesize_retval% LEQ 2048 ( + echo Discarding %2.debug + del %~1.debug + exit /b 0 +) +if not exist %~dp1.debug mkdir %~dp1.debug +move %~1.debug %~dp1.debug\ > NUL +strip %~1 +:: Add debuglink +:: FIXME: There is a problem with gdb that cause it to output this warning +:: FIXME: "warning: section .gnu_debuglink not found in xxx.debug" +:: FIXME: I tried adding a link to itself but this kills drmingw :( +objcopy --add-gnu-debuglink="%~dp1.debug\%~nx1.debug" %~1 +exit /b %ERRORLEVEL% + +:getfilesize +set getfilesize_retval=%~z1 +goto :eof + +:relpath_dirpath +call :relpath_dirpath_internal "" "%~1" +goto :eof + +:relpath_dirpath_internal +for /f "tokens=1* delims=\" %%a in ("%~2") do ( + :: If part 2 is empty, it means part 1 is probably the file name + if x%%b==x ( + set relpath_dirpath_retval=%~1 + ) else ( + call :relpath_dirpath_internal "%~1%%a\" %%b + ) +) +goto :eof diff --git a/packaging/windows/package.bat b/packaging/windows/package.bat deleted file mode 100644 index 40eef5b9fc..0000000000 --- a/packaging/windows/package.bat +++ /dev/null @@ -1,103 +0,0 @@ -:: This batch script is meant to prepare a Krita package folder to be zipped or -:: to be a base for the installer. -:: -:: Just drop it next to the "i" install folder where the dependencies and Krita binaries are. -:: -:: TODO: Ask if the user want to make an archive and with which tool -:: TODO: Maybe ask for a custom install folder name? -set ZIP="c:\Program Files\7-Zip" -set MINGW_GCC_BIN=c:\TDM-GCC-64\bin\ -set BUILDROOT=%CD% - -set BUILDDIR_INSTALL=%BUILDROOT%\i -set PATH=%MINGW_GCC_BIN%;%PATH% - -@echo off - -if not exist %BUILDDIR_INSTALL% ( -echo Cannot find the install folder! -pause -exit /B -) - -set /P pkg_root=Insert krita package name: - -if [%pkg_root%] == [] ( -echo You entered an empty name! -pause -exit /B -) - -:: Initial folder setup -mkdir %pkg_root% -mkdir %pkg_root%\bin -mkdir %pkg_root%\lib -mkdir %pkg_root%\share - -:: Bin folder -copy %MINGW_GCC_BIN%\lib*.dll %pkg_root%\bin -copy %BUILDDIR_INSTALL%\bin\krita.exe %pkg_root%\bin -copy %BUILDDIR_INSTALL%\bin\*.dll %pkg_root%\bin -copy %BUILDDIR_INSTALL%\lib\*.dll %pkg_root%\bin -xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\imageformats %pkg_root%\bin\imageformats -xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\kf5 %pkg_root%\bin\kf5 -xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\platforms\qwindows.dll %pkg_root%\bin\platforms\ -xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\printsupport %pkg_root%\bin\printsupport -xcopy /Y %BUILDDIR_INSTALL%\plugins\iconengines\*.dll %pkg_root%\bin\iconengines\ - -:: Translations -mkdir %pkg_root%\bin\translations -copy %BUILDDIR_INSTALL%\translations\qt_ca.qm %pkg_root%\bin\translations\qt_ca.qm -copy %BUILDDIR_INSTALL%\translations\qt_cs.qm %pkg_root%\bin\translations\qt_cs.qm -copy %BUILDDIR_INSTALL%\translations\qt_de.qm %pkg_root%\bin\translations\qt_de.qm -copy %BUILDDIR_INSTALL%\translations\qt_en.qm %pkg_root%\bin\translations\qt_en.qm -copy %BUILDDIR_INSTALL%\translations\qt_fi.qm %pkg_root%\bin\translations\qt_fi.qm -copy %BUILDDIR_INSTALL%\translations\qt_he.qm %pkg_root%\bin\translations\qt_hu.qm -copy %BUILDDIR_INSTALL%\translations\qt_it.qm %pkg_root%\bin\translations\qt_it.qm -copy %BUILDDIR_INSTALL%\translations\qt_ja.qm %pkg_root%\bin\translations\qt_ja.qm -copy %BUILDDIR_INSTALL%\translations\qt_ko.qm %pkg_root%\bin\translations\qt_ko.qm -copy %BUILDDIR_INSTALL%\translations\qt_lv.qm %pkg_root%\bin\translations\qt_lv.qm -copy %BUILDDIR_INSTALL%\translations\qt_ru.qm %pkg_root%\bin\translations\qt_ru.qm -copy %BUILDDIR_INSTALL%\translations\qt_sk.qm %pkg_root%\bin\translations\qt_sk.qm -copy %BUILDDIR_INSTALL%\translations\qt_uk.qm %pkg_root%\bin\translations\qt_uk.qm -copy %BUILDDIR_INSTALL%\translations\qt_fr.qm %pkg_root%\bin\translations\qt_fr.qm - -:: Lib -xcopy /Y %BUILDDIR_INSTALL%\lib\kritaplugins\*.dll %pkg_root%\lib\kritaplugins\ -strip %pkg_root%\lib\kritaplugins\*.dll - -:: Share -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\appdata %pkg_root%\share\appdata -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\applications %pkg_root%\share\applications -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color %pkg_root%\share\color -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color-schemes %pkg_root%\share\color-schemes -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\doc %pkg_root%\share\doc -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\icons %pkg_root%\share\icons -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kf5 %pkg_root%\share\kf5 -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\krita %pkg_root%\share\krita -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kritaplugins %pkg_root%\share\kritaplugins -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kservices5 %pkg_root%\share\kservices5 -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\locale %pkg_root%\bin\locale -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\man %pkg_root%\share\man -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\mime %pkg_root%\share\mime -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\ocio %pkg_root%\share\ocio - -::Link -copy %BUILDROOT%\krita\packaging\windows\krita.lnk %pkg_root% - -%BUILDDIR_INSTALL%\bin\windeployqt.exe %pkg_root%\bin\krita.exe - -:: Debug build - -%ZIP%\7z.exe a -tzip %pkg_root%-dbg.zip %pkg_root% - -:: Bin folder -strip %pkg_root%\bin\krita.exe -strip %pkg_root%\bin\*.dll -strip %pkg_root%\bin\imageformats\*.dll -strip %pkg_root%\bin\kf5\*.dll -strip %pkg_root%\bin\kf5\org.kde.kwindowsystem.platforms\*.dll -strip %pkg_root%\bin\platforms\*.dll -strip %pkg_root%\bin\iconengines\*.dll - -%ZIP%\7z.exe a -tzip %pkg_root%.zip %pkg_root% diff --git a/packaging/windows/package_2.cmd b/packaging/windows/package_2.cmd deleted file mode 100644 index ca3c4a6a90..0000000000 --- a/packaging/windows/package_2.cmd +++ /dev/null @@ -1,374 +0,0 @@ -@echo off -:: This batch script is meant to prepare a Krita package folder to be zipped or -:: to be a base for the installer. -:: -:: Just drop it next to the "i" install folder where the dependencies and Krita -:: binaries are. -:: -:: Also copy filelist_bin_dll.txt and filelist_lib_dll.txt if you want more -:: fine-grained DLL dependencies copying. -:: -:: You may want to review the following parameters. -:: -:: Configuration parameters: -:: -:: MINGW_GCC_BIN: Path to the mingw-w64/bin dir -:: SEVENZIP_EXE: Path to 7-Zip executable (either 7z.exe or 7zG.exe) -:: BUILDDIR_SRC: Path to krita source dir (for package shortcut) -:: BUILDDIR_INSTALL: Path to INSTALL prefix of the build -:: -:: Note that paths should only contain alphanumeric, _ and -, except for the -:: path to 7-Zip, which is fine if quoted. -:: -set MINGW_GCC_BIN=C:\mingw-x64\mingw64\bin -set SEVENZIP_EXE="C:\Program Files\7-Zip\7z.exe" -rem set BUILDDIR_SRC=%CD%\krita -set BUILDDIR_SRC=%CD%\krita -set BUILDDIR_INSTALL=%CD%\i - -:: -------- - -set PATH=%MINGW_GCC_BIN%;%PATH% - -echo Krita Windows packaging script -echo. -echo Configurations: -echo MINGW_GCC_BIN: %MINGW_GCC_BIN% -echo SEVENZIP_EXE: %SEVENZIP_EXE% -echo BUILDDIR_SRC: %BUILDDIR_SRC% -echo BUILDDIR_INSTALL: %BUILDDIR_INSTALL% -echo. - -if "%1" == "" ( - set PAUSE=pause -) else ( - set "PAUSE= " -) - -:: Simple checking -if not exist %BUILDDIR_INSTALL% ( - echo ERROR: Cannot find the install folder! - %PAUSE% - exit /B 1 -) -if not "%BUILDDIR_INSTALL: =%" == "%BUILDDIR_INSTALL%" ( - echo ERROR: Install path contains space, which will not work properly! - %PAUSE% - exit /B 1 -) - -:: Decide package name to use -if "%1" == "" ( - echo Please input a package name. It will be used for the output directory, package - echo file name and as the top-level directory of the package. - echo Alternatively, you can pass it as the first argument to this script. - echo You should only use alphanumeric, _ and - - echo. - set /P pkg_name=Package name^> - setlocal - if "!pkg_name!" == "" ( - echo ERROR: You cannot choose an empty name! - %PAUSE% - exit /B 1 - ) - endlocal -) else ( - set pkg_name=%1 -) -echo Package name is "%pkg_name%" - -set pkg_root=%CD%\%pkg_name% -echo Packaging dir is %pkg_root% -echo. -if exist %pkg_root% ( - echo ERROR: Packaging dir already exists! Please remove or rename it first. - %PAUSE% - exit /B 1 -) - -echo. -echo Trying to guess GCC version... -g++ --version > NUL -if errorlevel 1 ( - echo ERROR: g++ is not working. - %PAUSE% - exit /B 1 -) -for /f "delims=" %%a in ('g++ --version ^| find "g++"') do set GCC_VERSION_LINE=%%a -echo -- %GCC_VERSION_LINE% -if "%GCC_VERSION_LINE:tdm64=%" == "%GCC_VERSION_LINE%" ( - echo Compiler doesn't look like TDM64-GCC, assuming simple mingw-w64 - set IS_TDM= -) else ( - echo Compiler looks like TDM64-GCC - set IS_TDM=1 -) - -echo. -echo Trying to guess target architecture... -objdump --version > NUL -if errorlevel 1 ( - echo ERROR: objdump is not working. - %PAUSE% - exit /B 1 -) -for /f "delims=, tokens=1" %%a in ('objdump -f %BUILDDIR_INSTALL%\bin\krita.exe ^| find "architecture"') do set TARGET_ARCH_LINE=%%a -echo -- %TARGET_ARCH_LINE% -if "%TARGET_ARCH_LINE:x86-64=%" == "%TARGET_ARCH_LINE%" ( - echo Target looks like x86 - set IS_X64= -) else ( - echo Target looks like x64 - set IS_x64=1 -) - -echo. -echo Testing for objcopy... -objcopy --version > NUL -if errorlevel 1 ( - echo ERROR: objcopy is not working. - %PAUSE% - exit /B 1 -) - -echo. -echo Testing for strip... -strip --version > NUL -if errorlevel 1 ( - echo ERROR: strip is not working. - %PAUSE% - exit /B 1 -) - -echo. -echo Creating base directories... -mkdir %pkg_root% && ^ -mkdir %pkg_root%\bin && ^ -mkdir %pkg_root%\lib && ^ -mkdir %pkg_root%\share -if errorlevel 1 ( - echo ERROR: Cannot create packaging dir tree! - %PAUSE% - exit /B 1 -) - -echo. -echo Copying GCC libraries... -if x%IS_TDM% == x ( - if x%is_x64% == x ( - :: mingw-w64 x86 - set "STDLIBS=gcc_s_dw2-1 gomp-1 stdc++-6 winpthread-1" - ) else ( - :: mingw-w64 x64 - set "STDLIBS=gcc_s_seh-1 gomp-1 stdc++-6 winpthread-1" - ) -) else ( - if x%is_x64% == x ( - :: TDM-GCC x86 - set "STDLIBS=gomp-1" - ) else ( - :: TDM-GCC x64 - set "STDLIBS=gomp_64-1" - ) -) -for %%L in (%STDLIBS%) do copy "%MINGW_GCC_BIN%\lib%%L.dll" %pkg_root%\bin - -echo. -echo Copying files... -:: krita.exe -copy %BUILDDIR_INSTALL%\bin\krita.exe %pkg_root%\bin -:: DLLs from bin/ -if exist filelist_bin_dll.txt ( - for /f %%F in (filelist_bin_dll.txt) do copy %BUILDDIR_INSTALL%\bin\%%F %pkg_root%\bin -) else ( - echo INFO: filelist_bin_dll.txt not found, copying all DLLs except Qt5 from bin/ - setlocal enableextensions enabledelayedexpansion - for /f "delims=" %%F in ('dir /b "%BUILDDIR_INSTALL%\bin\*.dll"') do ( - set file=%%F - set file=!file:~0,3! - if not x!file! == xQt5 copy %BUILDDIR_INSTALL%\bin\%%F %pkg_root%\bin - ) - endlocal -) -:: symsrv.yes for Dr. Mingw -copy %BUILDDIR_INSTALL%\bin\symsrv.yes %pkg_root%\bin -:: DLLs from lib/ -if exist filelist_lib_dll.txt ( - for /f %%F in (filelist_lib_dll.txt) do copy %BUILDDIR_INSTALL%\lib\%%F %pkg_root%\bin -) else ( - echo INFO: filelist_lib_dll.txt not found, copying all DLLs from lib/ - copy %BUILDDIR_INSTALL%\lib\*.dll %pkg_root%\bin -) -:: Boost, there might be more than one leftover but we can't really do much -copy %BUILDDIR_INSTALL%\bin\libboost_system-*.dll %pkg_root%\bin -:: KF5 plugins may be placed at different locations depending on how Qt is built -xcopy /S /Y /I %BUILDDIR_INSTALL%\lib\plugins\imageformats %pkg_root%\bin\imageformats -xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\imageformats %pkg_root%\bin\imageformats -xcopy /S /Y /I %BUILDDIR_INSTALL%\lib\plugins\kf5 %pkg_root%\bin\kf5 -xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\kf5 %pkg_root%\bin\kf5 -:: Qt Translations -:: it seems that windeployqt does these, but only *some* of these??? -mkdir %pkg_root%\bin\translations -setlocal enableextensions enabledelayedexpansion -for /f "delims=" %%F in ('dir /b "%BUILDDIR_INSTALL%\translations\qt_*.qm"') do ( - :: Exclude qt_help_*.qm - set temp=%%F - set temp2=!temp:_help=! - if x!temp2! == x!temp! copy %BUILDDIR_INSTALL%\translations\!temp! %pkg_root%\bin\translations\!temp! -) -endlocal -:: Krita plugins -xcopy /Y %BUILDDIR_INSTALL%\lib\kritaplugins\*.dll %pkg_root%\lib\kritaplugins\ - -:: Share -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color %pkg_root%\share\color -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color-schemes %pkg_root%\share\color-schemes -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\icons %pkg_root%\share\icons -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kf5 %pkg_root%\share\kf5 -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\krita %pkg_root%\share\krita -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kritaplugins %pkg_root%\share\kritaplugins -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\mime %pkg_root%\share\mime -:: Not useful on Windows it seems -rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\appdata %pkg_root%\share\appdata -rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\applications %pkg_root%\share\applications -rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\doc %pkg_root%\share\doc -rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kservices5 %pkg_root%\share\kservices5 -rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\man %pkg_root%\share\man -rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\ocio %pkg_root%\share\ocio - -:: Copy locale to bin -xcopy /Y /S /I %BUILDDIR_INSTALL%\share\locale %pkg_root%\bin\locale - -:: Copy shortcut link from source (can't create it dynamically) -copy %BUILDDIR_SRC%\packaging\windows\krita.lnk %pkg_root% - -:: windeployqt -%BUILDDIR_INSTALL%\bin\windeployqt.exe --qmldir %BUILDDIR_INSTALL%\qml --release -gui -core -concurrent -network -printsupport -svg -xml -multimedia -qml -quick -quickwidgets %pkg_root%\bin\krita.exe - -if errorlevel 1 ( - echo ERROR: WinDeployQt failed!! - %PAUSE% - exit /B 1 -) - -if EXIST "%BUILDDIR_INSTALL%\bin\gmic_krita_qt.exe" ( - copy %BUILDDIR_INSTALL%\bin\gmic_krita_qt.exe %pkg_root%\bin - %BUILDDIR_INSTALL%\bin\windeployqt.exe --release %pkg_root%\bin\gmic_krita_qt.exe - if errorlevel 1 ( - echo ERROR: WinDeployQt failed!! - %PAUSE% - exit /B 1 - ) -) - - -:: Copy embedded Python -xcopy /Y /S /I %BUILDDIR_INSTALL%\python %pkg_root%\python - -:: For chopping relative path -:: 512 should be enough -:: n+2 to also account for a trailing backslash -setlocal enableextensions enabledelayedexpansion -for /L %%n in (1 1 512) do if "!pkg_root:~%%n,1!" neq "" set /a "pkg_root_len_plus_one=%%n+2" -endlocal & set pkg_root_len_plus_one=%pkg_root_len_plus_one% - -echo. -setlocal enableextensions enabledelayedexpansion -:: Remove Python cache files -for /d /r "%pkg_root%" %%F in (__pycache__\) do ( - if EXIST "%%F" ( - set relpath=%%F - set relpath=!relpath:~%pkg_root_len_plus_one%! - echo Deleting Python cache !relpath! - rmdir /S /Q "%%F" - ) -) -endlocal - -echo. -echo Splitting debug info from binaries... -call :split-debug "%pkg_root%\bin\krita.exe" bin\krita.exe -setlocal enableextensions enabledelayedexpansion -:: Find all DLLs -for /r "%pkg_root%" %%F in (*.dll) do ( - set relpath=%%F - set relpath=!relpath:~%pkg_root_len_plus_one%! - call :split-debug "%%F" !relpath! -) -endlocal -setlocal enableextensions enabledelayedexpansion -:: Find all Python native modules -for /r "%pkg_root%\share\krita\pykrita\" %%F in (*.pyd) do ( - set relpath=%%F - set relpath=!relpath:~%pkg_root_len_plus_one%! - call :split-debug "%%F" !relpath! -) -endlocal - -echo. -echo Packaging debug info... -:: (note that the top-level package dir is not included) -%SEVENZIP_EXE% a -tzip %pkg_name%-dbg.zip -r %pkg_root%\*.debug -echo -------- - -echo. -echo Packaging stripped binaries... -%SEVENZIP_EXE% a -tzip %pkg_name%.zip %pkg_root%\ -xr!.debug -echo -------- - -echo. -echo. -echo Krita packaged as %pkg_name%.zip -if exist %pkg_name%-dbg.zip echo Debug info packaged as %pkg_name%-dbg.zip -echo Packaging dir is %pkg_root% -echo NOTE: Do not create installer with packaging dir. Extract from -echo %pkg_name%.zip instead, -echo and do _not_ run krita inside the extracted directory because it will -echo create extra unnecessary files. -echo. -echo Please remember to actually test the package before releasing it. -echo. -%PAUSE% - -exit /b - -:split-debug -echo Splitting debug info of %2 -objcopy --only-keep-debug %~1 %~1.debug -if ERRORLEVEL 1 exit /b %ERRORLEVEL% -:: If the debug file is small enough then consider there being no debug info. -:: Discard these files since they somehow make gdb crash. -call :getfilesize %~1.debug -if /i %getfilesize_retval% LEQ 2048 ( - echo Discarding %2.debug - del %~1.debug - exit /b 0 -) -if not exist %~dp1.debug mkdir %~dp1.debug -move %~1.debug %~dp1.debug\ > NUL -strip %~1 -:: Add debuglink -:: FIXME: There is a problem with gdb that cause it to output this warning -:: FIXME: "warning: section .gnu_debuglink not found in xxx.debug" -:: FIXME: I tried adding a link to itself but this kills drmingw :( -objcopy --add-gnu-debuglink="%~dp1.debug\%~nx1.debug" %~1 -exit /b %ERRORLEVEL% - -:getfilesize -set getfilesize_retval=%~z1 -goto :eof - -:relpath_dirpath -call :relpath_dirpath_internal "" "%~1" -goto :eof - -:relpath_dirpath_internal -for /f "tokens=1* delims=\" %%a in ("%~2") do ( - :: If part 2 is empty, it means part 1 is probably the file name - if x%%b==x ( - set relpath_dirpath_retval=%~1 - ) else ( - call :relpath_dirpath_internal "%~1%%a\" %%b - ) -) -goto :eof diff --git a/pics/calligrakrita-base.svg b/pics/calligrakrita-base.svg index 8476d7b7f5..fa6403718a 100644 --- a/pics/calligrakrita-base.svg +++ b/pics/calligrakrita-base.svg @@ -1,2277 +1,2565 @@ + inkscape:export-ydpi="11.25" + enable-background="new"> + inkscape:window-maximized="0" + inkscape:current-layer="svg2985" + inkscape:snap-page="true" + inkscape:pagecheckerboard="true" /> + + + + + + + + + + + + + + + + + + + + + + image/svg+xml - + + - - - + style="display:inline" + inkscape:label="border" + clip-path="url(#clipPath1447)"> + style="display:inline;filter:url(#filter5334-2)" + inkscape:label="gradient_image_old" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> + + + + style="display:none" + inkscape:label="stroke"> + style="display:inline;fill:url(#linearGradient5021);fill-opacity:1;stroke:none;filter:url(#filter5042)" + inkscape:connector-curvature="0" /> + style="display:none" + inkscape:label="brush_krita_2_3"> + style="display:inline;fill:#000000;fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#7e7e7e;fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;opacity:0.75;fill:url(#linearGradient3948-4-3);fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;fill:url(#linearGradient4043-4-4);fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;fill:url(#linearGradient4060-91-5);fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient4129-0-1);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.69999999;marker:none;enable-background:accumulate" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient4095-7-1);fill-opacity:1;stroke:none;stroke-width:0.69999999;marker:none;filter:url(#filter4105-2-3);enable-background:accumulate" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;opacity:0.32000002;fill:url(#linearGradient4204-1-4);fill-opacity:1;stroke:none;stroke-width:0.69999999;marker:none;filter:url(#filter4105-4-2-4);enable-background:accumulate" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#000000;fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;fill:url(#linearGradient4964-70-4);fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;fill:url(#linearGradient4256-7-5);fill-opacity:1;stroke:none" + inkscape:connector-curvature="0" /> + style="display:inline;opacity:0.75;fill:url(#radialGradient5418-5);fill-opacity:1;stroke:none;filter:url(#filter5432-1)" + inkscape:connector-curvature="0" /> + style="display:inline;opacity:0.3;fill:url(#radialGradient5453-2);fill-opacity:1;stroke:none;filter:url(#filter5432-7-0)" + inkscape:connector-curvature="0" /> + + + + + + diff --git a/pics/calligrakrita.png b/pics/calligrakrita.png index 3e61d268aa..4de5c41e85 100644 Binary files a/pics/calligrakrita.png and b/pics/calligrakrita.png differ diff --git a/plugins/dockers/animation/kis_animation_utils.cpp b/plugins/dockers/animation/kis_animation_utils.cpp index 0e6fb5f05d..1e48595e33 100644 --- a/plugins/dockers/animation/kis_animation_utils.cpp +++ b/plugins/dockers/animation/kis_animation_utils.cpp @@ -1,266 +1,267 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_utils.h" #include "kundo2command.h" #include "kis_algebra_2d.h" #include "kis_image.h" #include "kis_node.h" #include "kis_keyframe_channel.h" #include "kis_post_execution_undo_adapter.h" #include "kis_global.h" #include "kis_tool_utils.h" #include "kis_image_animation_interface.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "kis_transaction.h" namespace KisAnimationUtils { const QString addFrameActionName = i18n("New Frame"); const QString duplicateFrameActionName = i18n("Copy Frame"); const QString removeFrameActionName = i18n("Remove Frame"); const QString removeFramesActionName = i18n("Remove Frames"); const QString lazyFrameCreationActionName = i18n("Auto Frame Mode"); const QString dropFramesActionName = i18n("Drop Frames"); const QString showLayerActionName = i18n("Show in Timeline"); const QString newLayerActionName = i18n("New Layer"); const QString addExistingLayerActionName = i18n("Add Existing Layer"); const QString removeLayerActionName = i18n("Remove Layer"); const QString addOpacityKeyframeActionName = i18n("Add opacity keyframe"); const QString addTransformKeyframeActionName = i18n("Add transform keyframe"); const QString removeOpacityKeyframeActionName = i18n("Remove opacity keyframe"); const QString removeTransformKeyframeActionName = i18n("Remove transform keyframe"); void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy) { KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( copy ? kundo2_i18n("Copy Keyframe") : kundo2_i18n("Add Keyframe"), [image, node, channelId, time, copy] () mutable -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); KisKeyframeChannel *channel = node->getKeyframeChannel(channelId); bool createdChannel = false; if (!channel) { node->enableAnimation(); channel = node->getKeyframeChannel(channelId, true); if (!channel) return nullptr; createdChannel = true; } if (copy) { if (!channel->keyframeAt(time)) { KisKeyframeSP srcFrame = channel->activeKeyframeAt(time); channel->copyKeyframe(srcFrame, time, cmd.data()); result = true; } } else { if (channel->keyframeAt(time) && !createdChannel) { if (image->animationInterface()->currentTime() == time && channelId == KisKeyframeChannel::Content.id()) { //shortcut: clearing the image instead KisPaintDeviceSP device = node->paintDevice(); if (device) { KisTransaction transaction(kundo2_i18n("Clear"), device, cmd.data()); device->clear(); (void) transaction.endAndTake(); // saved as 'parent' result = true; } } } else { channel->addKeyframe(time, cmd.data()); result = true; } } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : nullptr; }); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void removeKeyframes(KisImageSP image, const FrameItemList &frames) { KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( kundo2_i18np("Remove Keyframe", "Remove Keyframes", frames.size()), [image, frames] () { bool result = false; QScopedPointer cmd(new KUndo2Command()); Q_FOREACH (const FrameItem &item, frames) { const int time = item.time; KisNodeSP node = item.node; - - KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel); - + KisKeyframeChannel *channel; + if (node) { + channel = node->getKeyframeChannel(item.channel); + } if (!channel) continue; KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) continue; channel->deleteKeyframe(keyframe, cmd.data()); result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void removeKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int time) { QVector frames; frames << FrameItem(node, channel, time); removeKeyframes(image, frames); } struct LessOperator { LessOperator(const QPoint &offset) : m_columnCoeff(-KisAlgebra2D::signPZ(offset.x())), m_rowCoeff(-1000000 * KisAlgebra2D::signZZ(offset.y())) { } bool operator()(const QModelIndex &lhs, const QModelIndex &rhs) { return m_columnCoeff * lhs.column() + m_rowCoeff * lhs.row() < m_columnCoeff * rhs.column() + m_rowCoeff * rhs.row(); } private: int m_columnCoeff; int m_rowCoeff; }; void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset) { std::sort(points->begin(), points->end(), LessOperator(offset)); } KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy, KUndo2Command *parentCommand) { KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( !copy ? kundo2_i18np("Move Keyframe", "Move %1 Keyframes", srcFrames.size()) : kundo2_i18np("Copy Keyframe", "Copy %1 Keyframes", srcFrames.size()), parentCommand, [srcFrames, dstFrames, copy] () -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); for (int i = 0; i < srcFrames.size(); i++) { const int srcTime = srcFrames[i].time; KisNodeSP srcNode = srcFrames[i].node; KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(srcFrames[i].channel); const int dstTime = dstFrames[i].time; KisNodeSP dstNode = dstFrames[i].node; KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dstFrames[i].channel, true); if (srcNode == dstNode) { if (!srcChannel) continue; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); if (srcKeyframe) { if (copy) { srcChannel->copyKeyframe(srcKeyframe, dstTime, cmd.data()); } else { srcChannel->moveKeyframe(srcKeyframe, dstTime, cmd.data()); } } } else { if (!srcChannel|| !dstChannel) continue; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); if (!srcKeyframe) continue; dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, cmd.data()); if (!copy) { srcChannel->deleteKeyframe(srcKeyframe, cmd.data()); } } result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); return cmd; } void moveKeyframes(KisImageSP image, const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy) { KIS_SAFE_ASSERT_RECOVER_RETURN(srcFrames.size() != dstFrames.size()); KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = createMoveKeyframesCommand(srcFrames, dstFrames, copy); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void moveKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int srcTime, int dstTime) { QVector srcFrames; srcFrames << FrameItem(node, channel, srcTime); QVector dstFrames; dstFrames << FrameItem(node, channel, dstTime); moveKeyframes(image, srcFrames, dstFrames); } bool supportsContentFrames(KisNodeSP node) { return node->inherits("KisPaintLayer") || node->inherits("KisFilterMask") || node->inherits("KisTransparencyMask") || node->inherits("KisSelectionBasedLayer"); } } diff --git a/plugins/dockers/animation/kis_equalizer_widget.h b/plugins/dockers/animation/kis_equalizer_widget.h index 611a6c1d9e..ac13419a00 100644 --- a/plugins/dockers/animation/kis_equalizer_widget.h +++ b/plugins/dockers/animation/kis_equalizer_widget.h @@ -1,64 +1,64 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_EQUALIZER_WIDGET_H #define __KIS_EQUALIZER_WIDGET_H #include #include #include #include "kritaanimationdocker_export.h" class KRITAANIMATIONDOCKER_EXPORT KisEqualizerWidget : public QWidget { Q_OBJECT public: KisEqualizerWidget(int maxDistance, QWidget *parent); ~KisEqualizerWidget() override; struct EqualizerValues { int maxDistance; - QMap value; + QMap value; QMap state; }; EqualizerValues getValues() const; void setValues(const EqualizerValues &values); void toggleMasterSwitch(); void resizeEvent(QResizeEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; Q_SIGNALS: void sigConfigChanged(); private Q_SLOTS: void slotMasterColumnChanged(int, bool, int); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_EQUALIZER_WIDGET_H */ diff --git a/plugins/dockers/animation/onion_skins_docker.cpp b/plugins/dockers/animation/onion_skins_docker.cpp index 4de3005bb2..49a2ea3773 100644 --- a/plugins/dockers/animation/onion_skins_docker.cpp +++ b/plugins/dockers/animation/onion_skins_docker.cpp @@ -1,196 +1,196 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "onion_skins_docker.h" #include "ui_onion_skins_docker.h" #include #include #include #include "kis_icon_utils.h" #include "kis_image_config.h" #include "kis_onion_skin_compositor.h" #include "kis_signals_blocker.h" #include "KisViewManager.h" #include "kis_action_manager.h" #include "kis_action.h" #include #include "kis_equalizer_widget.h" #include "kis_color_filter_combo.h" OnionSkinsDocker::OnionSkinsDocker(QWidget *parent) : QDockWidget(i18n("Onion Skins"), parent), ui(new Ui::OnionSkinsDocker), m_updatesCompressor(300, KisSignalCompressor::FIRST_ACTIVE), m_toggleOnionSkinsAction(0) { QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); KisImageConfig config; ui->setupUi(mainWidget); ui->doubleTintFactor->setMinimum(0); ui->doubleTintFactor->setMaximum(100); ui->doubleTintFactor->setPrefix(i18n("Tint: ")); ui->doubleTintFactor->setSuffix("%"); ui->btnBackwardColor->setToolTip(i18n("Tint color for past frames")); ui->btnForwardColor->setToolTip(i18n("Tint color for future frames")); QVBoxLayout *layout = ui->slidersLayout; m_equalizerWidget = new KisEqualizerWidget(10, this); connect(m_equalizerWidget, SIGNAL(sigConfigChanged()), &m_updatesCompressor, SLOT(start())); layout->addWidget(m_equalizerWidget, 1); connect(ui->btnBackwardColor, SIGNAL(changed(KoColor)), &m_updatesCompressor, SLOT(start())); connect(ui->btnForwardColor, SIGNAL(changed(KoColor)), &m_updatesCompressor, SLOT(start())); connect(ui->doubleTintFactor, SIGNAL(valueChanged(qreal)), &m_updatesCompressor, SLOT(start())); connect(&m_updatesCompressor, SIGNAL(timeout()), SLOT(changed())); { const bool isShown = config.showAdditionalOnionSkinsSettings(); ui->btnShowHide->setChecked(isShown); connect(ui->btnShowHide, SIGNAL(toggled(bool)), SLOT(slotShowAdditionalSettings(bool))); slotShowAdditionalSettings(isShown); } QSet colors; for (int c=1; c<=8; c++) colors.insert(c); ui->cmbColorLabelFilter->updateAvailableLabels(colors); connect(ui->cmbColorLabelFilter, &KisColorFilterCombo::selectedColorsChanged, this, &OnionSkinsDocker::slotFilteredColorsChanged); loadSettings(); KisOnionSkinCompositor::instance()->configChanged(); resize(sizeHint()); } OnionSkinsDocker::~OnionSkinsDocker() { delete ui; } void OnionSkinsDocker::setCanvas(KoCanvasBase *canvas) { Q_UNUSED(canvas); } void OnionSkinsDocker::unsetCanvas() { } void OnionSkinsDocker::setMainWindow(KisViewManager *view) { KisActionManager *actionManager = view->actionManager(); m_toggleOnionSkinsAction = actionManager->createAction("toggle_onion_skin"); connect(m_toggleOnionSkinsAction, SIGNAL(triggered()), SLOT(slotToggleOnionSkins())); slotUpdateIcons(); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); } void OnionSkinsDocker::slotToggleOnionSkins() { m_equalizerWidget->toggleMasterSwitch(); } void OnionSkinsDocker::slotFilteredColorsChanged() { KisOnionSkinCompositor::instance()->setColorLabelFilter(ui->cmbColorLabelFilter->selectedColors()); KisOnionSkinCompositor::instance()->configChanged(); } void OnionSkinsDocker::slotUpdateIcons() { if (m_toggleOnionSkinsAction) { m_toggleOnionSkinsAction->setIcon(KisIconUtils::loadIcon("onion_skin_options")); } } void OnionSkinsDocker::slotShowAdditionalSettings(bool value) { ui->lblPrevColor->setVisible(value); ui->lblNextColor->setVisible(value); ui->btnBackwardColor->setVisible(value); ui->btnForwardColor->setVisible(value); ui->doubleTintFactor->setVisible(value); QIcon icon = KisIconUtils::loadIcon(value ? "arrow-down" : "arrow-up"); ui->btnShowHide->setIcon(icon); KisImageConfig config; config.setShowAdditionalOnionSkinsSettings(value); } void OnionSkinsDocker::changed() { KisImageConfig config; KisEqualizerWidget::EqualizerValues v = m_equalizerWidget->getValues(); config.setNumberOfOnionSkins(v.maxDistance); for (int i = -v.maxDistance; i <= v.maxDistance; i++) { config.setOnionSkinOpacity(i, v.value[i] * 255.0 / 100.0); config.setOnionSkinState(i, v.state[i]); } config.setOnionSkinTintFactor(ui->doubleTintFactor->value() * 255.0 / 100.0); config.setOnionSkinTintColorBackward(ui->btnBackwardColor->color().toQColor()); config.setOnionSkinTintColorForward(ui->btnForwardColor->color().toQColor()); KisOnionSkinCompositor::instance()->configChanged(); } void OnionSkinsDocker::loadSettings() { KisImageConfig config; KisSignalsBlocker b(ui->doubleTintFactor, ui->btnBackwardColor, ui->btnForwardColor, m_equalizerWidget); - ui->doubleTintFactor->setValue(config.onionSkinTintFactor() * 100.0 / 255); + ui->doubleTintFactor->setValue(qRound(config.onionSkinTintFactor() * 100.0 / 255)); KoColor bcol(KoColorSpaceRegistry::instance()->rgb8()); bcol.fromQColor(config.onionSkinTintColorBackward()); ui->btnBackwardColor->setColor(bcol); bcol.fromQColor(config.onionSkinTintColorForward()); ui->btnForwardColor->setColor(bcol); KisEqualizerWidget::EqualizerValues v; v.maxDistance = 10; for (int i = -v.maxDistance; i <= v.maxDistance; i++) { - v.value.insert(i, config.onionSkinOpacity(i) * 100.0 / 255.0); + v.value.insert(i, qRound(config.onionSkinOpacity(i) * 100.0 / 255.0)); v.state.insert(i, config.onionSkinState(i)); } m_equalizerWidget->setValues(v); } diff --git a/plugins/dockers/griddocker/grid_config_widget.cpp b/plugins/dockers/griddocker/grid_config_widget.cpp index 3dd1cde309..af844fd892 100644 --- a/plugins/dockers/griddocker/grid_config_widget.cpp +++ b/plugins/dockers/griddocker/grid_config_widget.cpp @@ -1,336 +1,334 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "grid_config_widget.h" #include "ui_grid_config_widget.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_debug.h" #include "kis_aspect_ratio_locker.h" #include "kis_int_parse_spin_box.h" struct GridConfigWidget::Private { Private() : guiSignalsBlocked(false) {} KisGridConfig gridConfig; KisGuidesConfig guidesConfig; bool guiSignalsBlocked; }; GridConfigWidget::GridConfigWidget(QWidget *parent) : QWidget(parent), ui(new Ui::GridConfigWidget), m_d(new Private) { ui->setupUi(this); ui->colorMain->setAlphaChannelEnabled(true); ui->colorSubdivision->setAlphaChannelEnabled(true); ui->colorGuides->setAlphaChannelEnabled(true); ui->angleLeftSpinbox->setSuffix(QChar(Qt::Key_degree)); ui->angleRightSpinbox->setSuffix(QChar(Qt::Key_degree)); ui->cellSpacingSpinbox->setSuffix(i18n(" px")); ui->gridTypeCombobox->addItem(i18n("Rectangle")); ui->gridTypeCombobox->addItem(i18n("Isometric")); ui->gridTypeCombobox->setCurrentIndex(0); // set to rectangle by default slotGridTypeChanged(); // update the UI to hide any elements we don't need connect(ui->gridTypeCombobox, SIGNAL(currentIndexChanged(int)), SLOT(slotGridTypeChanged())); m_isGridEnabled = false; setGridConfig(m_d->gridConfig); setGuidesConfig(m_d->guidesConfig); // hide offset UI elements if offset is disabled connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblXOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblYOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intXOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intYOffset, SLOT(setVisible(bool))); connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->offsetAspectButton, SLOT(setVisible(bool))); ui->lblXOffset->setVisible(false); ui->lblYOffset->setVisible(false); ui->intXOffset->setVisible(false); ui->intYOffset->setVisible(false); ui->offsetAspectButton->setVisible(false); connect(ui->chkShowGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->chkSnapToGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->chkShowGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->chkSnapToGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->chkLockGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->intSubdivision, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->angleLeftSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->angleRightSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->cellSpacingSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged())); - - connect(ui->selectMainStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->colorMain, SIGNAL(changed(const QColor&)), SLOT(slotGridGuiChanged())); connect(ui->selectSubdivisionStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged())); connect(ui->colorSubdivision, SIGNAL(changed(const QColor&)), SLOT(slotGridGuiChanged())); connect(ui->selectGuidesStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGuidesGuiChanged())); connect(ui->colorGuides, SIGNAL(changed(const QColor&)), SLOT(slotGuidesGuiChanged())); ui->chkOffset->setChecked(false); KisAspectRatioLocker *offsetLocker = new KisAspectRatioLocker(this); offsetLocker->connectSpinBoxes(ui->intXOffset, ui->intYOffset, ui->offsetAspectButton); KisAspectRatioLocker *spacingLocker = new KisAspectRatioLocker(this); spacingLocker->connectSpinBoxes(ui->intHSpacing, ui->intVSpacing, ui->spacingAspectButton); connect(offsetLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged())); connect(offsetLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged())); connect(spacingLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged())); connect(spacingLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged())); connect(ui->chkShowRulers,SIGNAL(toggled(bool)),SIGNAL(showRulersChanged(bool))); } GridConfigWidget::~GridConfigWidget() { delete ui; } void GridConfigWidget::setGridConfig(const KisGridConfig &value) { KisGridConfig currentConfig = fetchGuiGridConfig(); if (currentConfig == value) return; setGridConfigImpl(value); } void GridConfigWidget::setGuidesConfig(const KisGuidesConfig &value) { KisGuidesConfig currentConfig = fetchGuiGuidesConfig(); if (currentConfig == value) return; setGuidesConfigImpl(value); } void GridConfigWidget::setGridConfigImpl(const KisGridConfig &value) { m_d->gridConfig = value; m_d->guiSignalsBlocked = true; ui->offsetAspectButton->setKeepAspectRatio(m_d->gridConfig.offsetAspectLocked()); ui->spacingAspectButton->setKeepAspectRatio(m_d->gridConfig.spacingAspectLocked()); ui->chkShowGrid->setChecked(m_d->gridConfig.showGrid()); ui->intHSpacing->setValue(m_d->gridConfig.spacing().x()); ui->intVSpacing->setValue(m_d->gridConfig.spacing().y()); ui->intXOffset->setValue(m_d->gridConfig.offset().x()); ui->intYOffset->setValue(m_d->gridConfig.offset().y()); ui->intSubdivision->setValue(m_d->gridConfig.subdivision()); ui->chkSnapToGrid->setChecked(m_d->gridConfig.snapToGrid()); ui->angleLeftSpinbox->setValue(m_d->gridConfig.angleLeft()); ui->angleRightSpinbox->setValue(m_d->gridConfig.angleRight()); ui->cellSpacingSpinbox->setValue(m_d->gridConfig.cellSpacing()); ui->selectMainStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeMain())); ui->selectSubdivisionStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeSubdivision())); ui->gridTypeCombobox->setCurrentIndex(m_d->gridConfig.gridType()); ui->colorMain->setColor(m_d->gridConfig.colorMain()); ui->colorSubdivision->setColor(m_d->gridConfig.colorSubdivision()); m_d->guiSignalsBlocked = false; emit gridValueChanged(); } void GridConfigWidget::setGuidesConfigImpl(const KisGuidesConfig &value) { m_d->guidesConfig = value; m_d->guiSignalsBlocked = true; ui->chkShowGuides->setChecked(m_d->guidesConfig.showGuides()); ui->chkSnapToGuides->setChecked(m_d->guidesConfig.snapToGuides()); ui->chkLockGuides->setChecked(m_d->guidesConfig.lockGuides()); ui->selectGuidesStyle->setCurrentIndex(int(m_d->guidesConfig.guidesLineType())); ui->colorGuides->setColor(m_d->guidesConfig.guidesColor()); m_d->guiSignalsBlocked = false; emit guidesValueChanged(); } KisGridConfig GridConfigWidget::gridConfig() const { return m_d->gridConfig; } KisGuidesConfig GridConfigWidget::guidesConfig() const { return m_d->guidesConfig; } void GridConfigWidget::setGridDivision(int w, int h) { ui->intHSpacing->setMaximum(w); ui->intVSpacing->setMaximum(h); } KisGridConfig GridConfigWidget::fetchGuiGridConfig() const { KisGridConfig config; config.setShowGrid(ui->chkShowGrid->isChecked()); config.setSnapToGrid(ui->chkSnapToGrid->isChecked()); QPoint pt; pt.rx() = ui->intHSpacing->value(); pt.ry() = ui->intVSpacing->value(); config.setSpacing(pt); pt.rx() = ui->intXOffset->value(); pt.ry() = ui->intYOffset->value(); config.setOffset(pt); config.setSubdivision(ui->intSubdivision->value()); config.setAngleLeft(ui->angleLeftSpinbox->value()); config.setAngleRight(ui->angleRightSpinbox->value()); config.setCellSpacing(ui->cellSpacingSpinbox->value()); config.setGridType(ui->gridTypeCombobox->currentIndex()); config.setOffsetAspectLocked(ui->offsetAspectButton->keepAspectRatio()); config.setSpacingAspectLocked(ui->spacingAspectButton->keepAspectRatio()); config.setLineTypeMain(KisGridConfig::LineTypeInternal(ui->selectMainStyle->currentIndex())); config.setLineTypeSubdivision(KisGridConfig::LineTypeInternal(ui->selectSubdivisionStyle->currentIndex())); config.setColorMain(ui->colorMain->color()); config.setColorSubdivision(ui->colorSubdivision->color()); return config; } KisGuidesConfig GridConfigWidget::fetchGuiGuidesConfig() const { KisGuidesConfig config = m_d->guidesConfig; config.setShowGuides(ui->chkShowGuides->isChecked()); config.setSnapToGuides(ui->chkSnapToGuides->isChecked()); config.setLockGuides(ui->chkLockGuides->isChecked()); config.setGuidesLineType(KisGuidesConfig::LineTypeInternal(ui->selectGuidesStyle->currentIndex())); config.setGuidesColor(ui->colorGuides->color()); return config; } void GridConfigWidget::slotGridGuiChanged() { if (m_d->guiSignalsBlocked) return; KisGridConfig currentConfig = fetchGuiGridConfig(); if (currentConfig == m_d->gridConfig) return; setGridConfigImpl(currentConfig); } void GridConfigWidget::slotGuidesGuiChanged() { if (m_d->guiSignalsBlocked) return; KisGuidesConfig currentConfig = fetchGuiGuidesConfig(); if (currentConfig == m_d->guidesConfig) return; setGuidesConfigImpl(currentConfig); } void GridConfigWidget::slotGridTypeChanged() { bool showRectangleControls = ui->gridTypeCombobox->currentIndex() == 0; // specific rectangle UI controls ui->lblXSpacing->setVisible(showRectangleControls); ui->lblYSpacing->setVisible(showRectangleControls); ui->intHSpacing->setVisible(showRectangleControls); ui->intVSpacing->setVisible(showRectangleControls); ui->spacingAspectButton->setVisible(showRectangleControls); ui->lblSubdivision->setVisible(showRectangleControls); ui->intSubdivision->setVisible(showRectangleControls); ui->lblSubdivisionStyle->setVisible(showRectangleControls); ui->selectSubdivisionStyle->setVisible(showRectangleControls); ui->colorSubdivision->setVisible(showRectangleControls); // specific isometric UI controls ui->leftAngleLabel->setVisible(!showRectangleControls); ui->rightAngleLabel->setVisible(!showRectangleControls); ui->angleLeftSpinbox->setVisible(!showRectangleControls); ui->angleRightSpinbox->setVisible(!showRectangleControls); ui->cellSpacingLabel->setVisible(!showRectangleControls); ui->cellSpacingSpinbox->setVisible(!showRectangleControls); // disable snapping for isometric grid type for now // remember if we had snapping enabled if it was on the rectangule mode if (!showRectangleControls) { m_isGridEnabled = ui->chkSnapToGrid->isChecked(); ui->chkSnapToGrid->setEnabled(false); ui->chkSnapToGrid->setChecked(false); } else { ui->chkSnapToGrid->setEnabled(true); ui->chkSnapToGrid->setChecked(m_isGridEnabled); } slotGridGuiChanged(); } bool GridConfigWidget::showRulers() const { return ui->chkShowRulers->isChecked(); } void GridConfigWidget::setShowRulers(bool value) { ui->chkShowRulers->setChecked(value); } diff --git a/plugins/dockers/griddocker/grid_config_widget.ui b/plugins/dockers/griddocker/grid_config_widget.ui index d6a24494c7..fc382749f8 100644 --- a/plugins/dockers/griddocker/grid_config_widget.ui +++ b/plugins/dockers/griddocker/grid_config_widget.ui @@ -1,723 +1,723 @@ GridConfigWidget 0 0 258 527 30 0 0 0 187 280 - 1 + 0 Grid QLayout::SetDefaultConstraint 0 0 Show grid 0 0 Snap to grid 0 0 Type: 0 0 QLayout::SetDefaultConstraint 0 0 10 1000 30 0 0 0 89 0 0 Cell Spacing: 0 0 0 89 1 30 0 0 Right Angle: 0 0 Left Angle: 0 0 X spacing: px 1 500 10 0 0 Y spacing: px 1 500 10 Subdivision: 0 0 1 10 2 0 0 30 0 - + 0 0 0 - + 0 0 0 0 0 Div Style: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 1 Lines Dashed Dots 0 0 30 0 0 Lines Dashed Dots 0 0 Main Style: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 107 22 0 0 Grid Offset false 0 0 px 500 0 0 0 X offset: 0 0 Y offset: 0 0 px 500 0 0 0 24 24 Qt::Vertical 20 40 Guides Show guides Snap to guides Lock guides Show rulers 0 0 Guides: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 Lines Dashed Dots 0 0 30 0 - + 0 0 0 - + 0 0 0 Qt::Vertical 20 40 + + KComboBox + QComboBox +
kcombobox.h
+
+ + KColorButton + QPushButton +
kcolorbutton.h
+ 1 +
KoAspectButton QWidget
KoAspectButton.h
1
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
- - KColorButton - QPushButton -
kcolorbutton.h
- 1 -
- - KComboBox - QComboBox -
kcombobox.h
-
diff --git a/plugins/dockers/griddocker/griddocker_dock.cpp b/plugins/dockers/griddocker/griddocker_dock.cpp index 797cc42173..951534ab20 100644 --- a/plugins/dockers/griddocker/griddocker_dock.cpp +++ b/plugins/dockers/griddocker/griddocker_dock.cpp @@ -1,118 +1,122 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "griddocker_dock.h" //#include "gridwidget.h" // #include // #include #include #include -#include "kis_canvas2.h" +#include #include #include -#include "kis_image.h" -#include "kis_paint_device.h" -#include "kis_signal_compressor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "grid_config_widget.h" -#include "kis_grid_manager.h" -#include "kis_grid_config.h" -#include "kis_guides_manager.h" -#include "kis_guides_config.h" -#include "kis_action.h" GridDockerDock::GridDockerDock( ) : QDockWidget(i18n("Grid and Guides")) , m_canvas(0) { m_configWidget = new GridConfigWidget(this); connect(m_configWidget, SIGNAL(gridValueChanged()), SLOT(slotGuiGridConfigChanged())); connect(m_configWidget, SIGNAL(guidesValueChanged()), SLOT(slotGuiGuidesConfigChanged())); setWidget(m_configWidget); setEnabled(m_canvas); } GridDockerDock::~GridDockerDock() { } void GridDockerDock::setCanvas(KoCanvasBase * canvas) { if(canvas && m_canvas == canvas) return; if (m_canvas) { m_canvasConnections.clear(); m_canvas->disconnectCanvasObserver(this); m_canvas->image()->disconnect(this); } m_canvas = canvas ? dynamic_cast(canvas) : 0; setEnabled(m_canvas); if (m_canvas) { m_canvasConnections.addConnection( m_canvas->viewManager()->gridManager(), SIGNAL(sigRequestUpdateGridConfig(const KisGridConfig&)), this, SLOT(slotGridConfigUpdateRequested(const KisGridConfig&))); + slotGridConfigUpdateRequested(m_canvas->viewManager()->document()->gridConfig()); + KisAction* action = m_canvas->viewManager()->actionManager()->actionByName("view_ruler"); m_canvasConnections.addConnection(m_configWidget,SIGNAL(showRulersChanged(bool)),action,SLOT(setChecked(bool))); m_canvasConnections.addConnection(action,SIGNAL(toggled(bool)),m_configWidget,SLOT(setShowRulers(bool))); m_configWidget->setShowRulers(action->isChecked()); m_canvasConnections.addConnection( m_canvas->viewManager()->guidesManager(), SIGNAL(sigRequestUpdateGuidesConfig(const KisGuidesConfig&)), this, SLOT(slotGuidesConfigUpdateRequested(const KisGuidesConfig&))); - + slotGuidesConfigUpdateRequested(m_canvas->viewManager()->document()->guidesConfig()); QRect rc = m_canvas->image()->bounds(); m_configWidget->setGridDivision(rc.width() / 2, rc.height() / 2); } } void GridDockerDock::unsetCanvas() { setCanvas(0); } void GridDockerDock::slotGuiGridConfigChanged() { if (!m_canvas) return; m_canvas->viewManager()->gridManager()->setGridConfig(m_configWidget->gridConfig()); } void GridDockerDock::slotGridConfigUpdateRequested(const KisGridConfig &config) { m_configWidget->setGridConfig(config); } void GridDockerDock::slotGuiGuidesConfigChanged() { if (!m_canvas) return; m_canvas->viewManager()->guidesManager()->setGuidesConfig(m_configWidget->guidesConfig()); } void GridDockerDock::slotGuidesConfigUpdateRequested(const KisGuidesConfig &config) { m_configWidget->setGuidesConfig(config); } diff --git a/plugins/extensions/bigbrother/bigbrother.cc b/plugins/extensions/bigbrother/bigbrother.cc index 2df5da991c..4b5c37b393 100644 --- a/plugins/extensions/bigbrother/bigbrother.cc +++ b/plugins/extensions/bigbrother/bigbrother.cc @@ -1,238 +1,238 @@ /* * Copyright (c) 2007 Cyrille Berger (cberger@cberger.net) * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "bigbrother.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 "actionseditor/kis_actions_editor.h" #include "actionseditor/kis_actions_editor_dialog.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(BigBrotherPluginFactory, "kritabigbrother.json", registerPlugin();) class RecordedActionSaveContext : public KisRecordedActionSaveContext { public: void saveGradient(const KoAbstractGradient* ) override {} void savePattern(const KoPattern* ) override {} }; class RecordedActionLoadContext : public KisRecordedActionLoadContext { public: KoAbstractGradient* gradient(const QString& name) const override { return KoResourceServerProvider::instance()->gradientServer()->resourceByName(name); } KoPattern* pattern(const QString& name) const override { return KoResourceServerProvider::instance()->patternServer()->resourceByName(name); } }; BigBrotherPlugin::BigBrotherPlugin(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_recorder(0) { if (parent->inherits("KisViewManager")) { m_view = (KisViewManager*) parent; // Open and play action KisAction* action = createAction("Macro_Open_Play"); connect(action, SIGNAL(triggered()), this, SLOT(slotOpenPlay())); // Open and edit action action = createAction("Macro_Open_Edit"); connect(action, SIGNAL(triggered()), this, SLOT(slotOpenEdit())); // Start recording action m_startRecordingMacroAction = createAction("Recording_Start_Recording_Macro"); connect(m_startRecordingMacroAction, SIGNAL(triggered()), this, SLOT(slotStartRecordingMacro())); // Save recorded action m_stopRecordingMacroAction = createAction("Recording_Stop_Recording_Macro"); connect(m_stopRecordingMacroAction, SIGNAL(triggered()), this, SLOT(slotStopRecordingMacro())); m_stopRecordingMacroAction->setEnabled(false); } } BigBrotherPlugin::~BigBrotherPlugin() { m_view = 0; delete m_recorder; } void BigBrotherPlugin::slotOpenPlay() { KisMacro* m = openMacro(); dbgKrita << m; if (!m) return; dbgPlugins << "Play the macro"; KoUpdaterPtr updater = m_view->createUnthreadedUpdater(i18n("Playing back macro")); KisMacroPlayer player(m, KisPlayInfo(m_view->image(), m_view->activeNode()), updater); player.start(); while(player.isRunning()) { QApplication::processEvents(); } dbgPlugins << "Finished"; delete m; } void BigBrotherPlugin::slotOpenEdit() { KisMacro *macro = openMacro(); if (!macro) return; KisActionsEditorDialog aed(m_view->mainWindow()); aed.actionsEditor()->setMacro(macro); if (aed.exec() == QDialog::Accepted) { saveMacro(macro); } delete macro; } void BigBrotherPlugin::slotStartRecordingMacro() { dbgPlugins << "Start recording macro"; if (m_recorder) return; // Alternate actions m_startRecordingMacroAction->setEnabled(false); m_stopRecordingMacroAction->setEnabled(true); // Create recorder m_recorder = new KisMacro(); connect(m_view->image()->actionRecorder(), SIGNAL(addedAction(const KisRecordedAction&)), m_recorder, SLOT(addAction(const KisRecordedAction&))); } void BigBrotherPlugin::slotStopRecordingMacro() { dbgPlugins << "Stop recording macro"; if (!m_recorder) return; // Alternate actions m_startRecordingMacroAction->setEnabled(true); m_stopRecordingMacroAction->setEnabled(false); // Save the macro saveMacro(m_recorder); // Delete recorder delete m_recorder; m_recorder = 0; } KisMacro* BigBrotherPlugin::openMacro() { QStringList mimeFilter; mimeFilter << "*.krarec|Recorded actions (*.krarec)"; KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenFile, "OpenDocument"); dialog.setCaption(i18n("Open Macro")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); - dialog.setMimeTypeFilters(QStringList() << "application/krita-recorded-macro", "application/krita-recorded-macro"); + dialog.setMimeTypeFilters(QStringList() << "application/x-krita-recorded-macro"); QString filename = dialog.filename(); RecordedActionLoadContext loadContext; if (!filename.isNull()) { QDomDocument doc; QFile f(filename); if (f.exists()) { dbgPlugins << f.open(QIODevice::ReadOnly); QString err; int line, col; if (!doc.setContent(&f, &err, &line, &col)) { // TODO error message dbgPlugins << err << " line = " << line << " col = " << col; f.close(); return 0; } f.close(); QDomElement docElem = doc.documentElement(); if (!docElem.isNull() && docElem.tagName() == "RecordedActions") { dbgPlugins << "Load the macro"; KisMacro* m = new KisMacro(); m->fromXML(docElem, &loadContext); return m; } else { // TODO error message } } else { dbgPlugins << "Unexistant file : " << filename; } } return 0; } void BigBrotherPlugin::saveMacro(const KisMacro* macro) { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "bigbrother"); dialog.setCaption(i18n("Save Macro")); - dialog.setMimeTypeFilters(QStringList() << "application/krita-recorded-macro", "application/krita-recorded-macro"); + dialog.setMimeTypeFilters(QStringList() << "application/x-krita-recorded-macro"); QString filename = dialog.filename(); if (!filename.isNull()) { QDomDocument doc; QDomElement e = doc.createElement("RecordedActions"); RecordedActionSaveContext context; macro->toXML(doc, e, &context); doc.appendChild(e); QFile f(filename); f.open(QIODevice::WriteOnly); QTextStream stream(&f); stream.setCodec("UTF-8"); doc.save(stream, 2); f.close(); } } #include "bigbrother.moc" diff --git a/plugins/extensions/pykrita/sip/CMakeLists.txt b/plugins/extensions/pykrita/sip/CMakeLists.txt index 994eb25a0c..46980d9907 100644 --- a/plugins/extensions/pykrita/sip/CMakeLists.txt +++ b/plugins/extensions/pykrita/sip/CMakeLists.txt @@ -1,29 +1,29 @@ include(SIPMacros) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../libkis) 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} - ${SIP_DEFAULT_SIP_DIR}/PyQt5 + ${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) set(PYTHON_SITE_PACKAGES_INSTALL_DIR ${DATA_INSTALL_DIR}/krita/pykrita/) 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) #install(FILES # ./__init__.py # DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR}) diff --git a/plugins/filters/asccdl/wdg_asccdl.ui b/plugins/filters/asccdl/wdg_asccdl.ui index be3f17056d..48b8aa3427 100644 --- a/plugins/filters/asccdl/wdg_asccdl.ui +++ b/plugins/filters/asccdl/wdg_asccdl.ui @@ -1,105 +1,102 @@ WdgASCCDL 0 0 400 300 - - - PushButton PushButton Offset: ASC-CDL color balance PushButton Power: Slope: Qt::Vertical 20 40 KisColorButton QPushButton
kis_color_button.h
KisVisualColorSelector QWidget
KisVisualColorSelector.h
1
diff --git a/plugins/flake/artistictextshape/ArtisticTextShape.cpp b/plugins/flake/artistictextshape/ArtisticTextShape.cpp index d657411ff1..e6ffec0866 100644 --- a/plugins/flake/artistictextshape/ArtisticTextShape.cpp +++ b/plugins/flake/artistictextshape/ArtisticTextShape.cpp @@ -1,1382 +1,1402 @@ /* This file is part of the KDE project * Copyright (C) 2007-2009,2011 Jan Hambrecht * Copyright (C) 2008 Rob Buis * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ArtisticTextShape.h" #include "ArtisticTextLoadingContext.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ArtisticTextShape::ArtisticTextShape() : m_path(0) , m_startOffset(0.0) , m_textAnchor(AnchorStart) , m_textUpdateCounter(0) , m_defaultFont("ComicSans", 20) { setShapeId(ArtisticTextShapeID); cacheGlyphOutlines(); updateSizeAndPosition(); } ArtisticTextShape::~ArtisticTextShape() { if (m_path) { m_path->removeDependee(this); } } +KoShape *ArtisticTextShape::cloneShape() const +{ + ArtisticTextShape *clone = new ArtisticTextShape(); + clone->m_ranges = m_ranges; + if (m_path) { + clone->m_path = static_cast(m_path->cloneShape()); + } + clone->m_charOutlines = m_charOutlines; + clone->m_startOffset = m_startOffset; + clone->m_outlineOrigin = m_outlineOrigin; + clone->m_outline = m_outline; + clone->m_baseline = m_baseline; + clone->m_textAnchor = m_textAnchor; + clone->m_charOffsets = m_charOffsets; + clone->m_charPositions = m_charPositions; + clone->m_textUpdateCounter = m_textUpdateCounter; + clone->m_defaultFont = m_defaultFont; + return clone; +} + void ArtisticTextShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); applyConversion(painter, converter); if (background()) { background()->paint(painter, converter, paintContext, outline()); } } void ArtisticTextShape::saveOdf(KoShapeSavingContext &context) const { SvgWriter svgWriter(QList() << const_cast(this)); QByteArray fileContent; QBuffer fileContentDevice(&fileContent); if (!fileContentDevice.open(QIODevice::WriteOnly)) { return; } if (!svgWriter.save(fileContentDevice, size())) { qWarning() << "Could not write svg content"; return; } const QString fileName = context.embeddedSaver().getFilename("SvgImages/Image"); const QString mimeType = "image/svg+xml"; context.xmlWriter().startElement("draw:frame"); context.embeddedSaver().embedFile(context.xmlWriter(), "draw:image", fileName, mimeType.toLatin1(), fileContent); context.xmlWriter().endElement(); // draw:frame } bool ArtisticTextShape::loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/) { return false; } QSizeF ArtisticTextShape::size() const { if (m_ranges.isEmpty()) { return nullBoundBox().size(); } else { return outline().boundingRect().size(); } } void ArtisticTextShape::setSize(const QSizeF &newSize) { QSizeF oldSize = size(); if (!oldSize.isNull()) { qreal zoomX = newSize.width() / oldSize.width(); qreal zoomY = newSize.height() / oldSize.height(); QTransform matrix(zoomX, 0, 0, zoomY, 0, 0); update(); applyTransformation(matrix); update(); } KoShape::setSize(newSize); } QPainterPath ArtisticTextShape::outline() const { return m_outline; } QRectF ArtisticTextShape::nullBoundBox() const { QFontMetrics metrics(defaultFont()); QPointF tl(0.0, -metrics.ascent()); QPointF br(metrics.averageCharWidth(), metrics.descent()); return QRectF(tl, br); } QFont ArtisticTextShape::defaultFont() const { return m_defaultFont; } qreal baselineShiftForFontSize(const ArtisticTextRange &range, qreal fontSize) { switch (range.baselineShift()) { case ArtisticTextRange::Sub: return fontSize / 3.; // taken from wikipedia case ArtisticTextRange::Super: return -fontSize / 3.; // taken from wikipedia case ArtisticTextRange::Percent: return range.baselineShiftValue() * fontSize; case ArtisticTextRange::Length: return range.baselineShiftValue(); default: return 0.0; } } QVector ArtisticTextShape::calculateAbstractCharacterPositions() { const int totalTextLength = plainText().length(); QVector charPositions; // one more than the number of characters for position after the last character charPositions.resize(totalTextLength + 1); // the character index within the text shape int globalCharIndex = 0; QPointF charPos(0, 0); QPointF advance(0, 0); const bool attachedToPath = isOnPath(); Q_FOREACH (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); const QString textRange = range.text(); const qreal letterSpacing = range.letterSpacing(); const int localTextLength = textRange.length(); const bool absoluteXOffset = range.xOffsetType() == ArtisticTextRange::AbsoluteOffset; const bool absoluteYOffset = range.yOffsetType() == ArtisticTextRange::AbsoluteOffset; // set baseline shift const qreal baselineShift = baselineShiftForFontSize(range, defaultFont().pointSizeF()); for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) { // apply offset to character if (range.hasXOffset(localCharIndex)) { if (absoluteXOffset) { charPos.rx() = range.xOffset(localCharIndex); } else { charPos.rx() += range.xOffset(localCharIndex); } } else { charPos.rx() += advance.x(); } if (range.hasYOffset(localCharIndex)) { if (absoluteYOffset) { // when attached to a path, absolute y-offsets are ignored if (!attachedToPath) { charPos.ry() = range.yOffset(localCharIndex); } } else { charPos.ry() += range.yOffset(localCharIndex); } } else { charPos.ry() += advance.y(); } // apply baseline shift charPos.ry() += baselineShift; // save character position of current character charPositions[globalCharIndex] = charPos; // advance character position advance = QPointF(metrics.width(textRange[localCharIndex]) + letterSpacing, 0.0); charPos.ry() -= baselineShift; } } charPositions[globalCharIndex] = charPos + advance; return charPositions; } void ArtisticTextShape::createOutline() { // reset relevant data m_outline = QPainterPath(); m_charPositions.clear(); m_charOffsets.clear(); // calculate character positions in baseline coordinates m_charPositions = calculateAbstractCharacterPositions(); // the character index within the text shape int globalCharIndex = 0; if (isOnPath()) { // one more than the number of characters for offset after the last character m_charOffsets.insert(0, m_charPositions.size(), -1); // the current character position qreal startCharOffset = m_startOffset * m_baseline.length(); // calculate total text width qreal totalTextWidth = 0.0; foreach (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); totalTextWidth += metrics.width(range.text()); } // adjust starting character position to anchor point if (m_textAnchor == AnchorMiddle) { startCharOffset -= 0.5 * totalTextWidth; } else if (m_textAnchor == AnchorEnd) { startCharOffset -= totalTextWidth; } QPointF pathPoint; qreal rotation = 0.0; qreal charOffset; foreach (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); const QString localText = range.text(); const int localTextLength = localText.length(); for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) { QPointF charPos = m_charPositions[globalCharIndex]; // apply advance along baseline charOffset = startCharOffset + charPos.x(); const qreal charMidPoint = charOffset + 0.5 * metrics.width(localText[localCharIndex]); // get the normalized position of the middle of the character const qreal midT = m_baseline.percentAtLength(charMidPoint); // is the character midpoint beyond the baseline ends? if (midT <= 0.0 || midT >= 1.0) { if (midT >= 1.0) { pathPoint = m_baseline.pointAtPercent(1.0); for (int i = globalCharIndex; i < m_charPositions.size(); ++i) { m_charPositions[i] = pathPoint; m_charOffsets[i] = 1.0; } break; } else { m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(0.0); m_charOffsets[globalCharIndex] = 0.0; continue; } } // get the percent value of the actual char position qreal t = m_baseline.percentAtLength(charOffset); // get the path point of the given path position pathPoint = m_baseline.pointAtPercent(t); // save character offset as fraction of baseline length m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(charOffset); // save character position as point m_charPositions[globalCharIndex] = pathPoint; // get the angle at the given path position const qreal angle = m_baseline.angleAtPercent(midT); if (range.hasRotation(localCharIndex)) { rotation = range.rotation(localCharIndex); } QTransform m; m.translate(pathPoint.x(), pathPoint.y()); m.rotate(360. - angle + rotation); m.translate(0.0, charPos.y()); m_outline.addPath(m.map(m_charOutlines[globalCharIndex])); } } // save offset and position after last character m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(startCharOffset + m_charPositions[globalCharIndex].x()); m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(m_charOffsets[globalCharIndex]); } else { qreal rotation = 0.0; Q_FOREACH (const ArtisticTextRange &range, m_ranges) { const QString textRange = range.text(); const int localTextLength = textRange.length(); for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) { const QPointF &charPos = m_charPositions[globalCharIndex]; if (range.hasRotation(localCharIndex)) { rotation = range.rotation(localCharIndex); } QTransform m; m.translate(charPos.x(), charPos.y()); m.rotate(rotation); m_outline.addPath(m.map(m_charOutlines[globalCharIndex])); } } } } void ArtisticTextShape::setPlainText(const QString &newText) { if (plainText() == newText) { return; } beginTextUpdate(); if (newText.isEmpty()) { // remove all text ranges m_ranges.clear(); } else if (isEmpty()) { // create new text range m_ranges.append(ArtisticTextRange(newText, defaultFont())); } else { // set text to first range m_ranges.first().setText(newText); // remove all ranges except the first while (m_ranges.count() > 1) { m_ranges.pop_back(); } } finishTextUpdate(); } QString ArtisticTextShape::plainText() const { QString allText; Q_FOREACH (const ArtisticTextRange &range, m_ranges) { allText += range.text(); } return allText; } QList ArtisticTextShape::text() const { return m_ranges; } bool ArtisticTextShape::isEmpty() const { return m_ranges.isEmpty(); } void ArtisticTextShape::clear() { beginTextUpdate(); m_ranges.clear(); finishTextUpdate(); } void ArtisticTextShape::setFont(const QFont &newFont) { // no text if (isEmpty()) { return; } const int rangeCount = m_ranges.count(); // only one text range with the same font if (rangeCount == 1 && m_ranges.first().font() == newFont) { return; } beginTextUpdate(); // set font on ranges for (int i = 0; i < rangeCount; ++i) { m_ranges[i].setFont(newFont); } m_defaultFont = newFont; finishTextUpdate(); } void ArtisticTextShape::setFont(int charIndex, int charCount, const QFont &font) { if (isEmpty() || charCount <= 0) { return; } if (charIndex == 0 && charCount == plainText().length()) { setFont(font); return; } CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || charPos.first >= m_ranges.count()) { return; } beginTextUpdate(); int remainingCharCount = charCount; while (remainingCharCount > 0) { ArtisticTextRange &currRange = m_ranges[charPos.first]; // does this range have a different font ? if (currRange.font() != font) { if (charPos.second == 0 && currRange.text().length() < remainingCharCount) { // set font on all characters of this range currRange.setFont(font); remainingCharCount -= currRange.text().length(); } else { ArtisticTextRange changedRange = currRange.extract(charPos.second, remainingCharCount); changedRange.setFont(font); if (charPos.second == 0) { m_ranges.insert(charPos.first, changedRange); } else if (charPos.second >= currRange.text().length()) { m_ranges.insert(charPos.first + 1, changedRange); } else { ArtisticTextRange remainingRange = currRange.extract(charPos.second); m_ranges.insert(charPos.first + 1, changedRange); m_ranges.insert(charPos.first + 2, remainingRange); } charPos.first++; remainingCharCount -= changedRange.text().length(); } } charPos.first++; if (charPos.first >= m_ranges.count()) { break; } charPos.second = 0; } finishTextUpdate(); } QFont ArtisticTextShape::fontAt(int charIndex) const { if (isEmpty()) { return defaultFont(); } if (charIndex < 0) { return m_ranges.first().font(); } const int rangeIndex = indexOfChar(charIndex).first; if (rangeIndex < 0) { return m_ranges.last().font(); } return m_ranges[rangeIndex].font(); } void ArtisticTextShape::setStartOffset(qreal offset) { if (m_startOffset == offset) { return; } update(); m_startOffset = qBound(0.0, offset, 1.0); updateSizeAndPosition(); update(); notifyChanged(); } qreal ArtisticTextShape::startOffset() const { return m_startOffset; } qreal ArtisticTextShape::baselineOffset() const { return m_charPositions.value(0).y(); } void ArtisticTextShape::setTextAnchor(TextAnchor anchor) { if (anchor == m_textAnchor) { return; } qreal totalTextWidth = 0.0; foreach (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); totalTextWidth += metrics.width(range.text()); } qreal oldOffset = 0.0; if (m_textAnchor == AnchorMiddle) { oldOffset = -0.5 * totalTextWidth; } else if (m_textAnchor == AnchorEnd) { oldOffset = -totalTextWidth; } m_textAnchor = anchor; qreal newOffset = 0.0; if (m_textAnchor == AnchorMiddle) { newOffset = -0.5 * totalTextWidth; } else if (m_textAnchor == AnchorEnd) { newOffset = -totalTextWidth; } update(); updateSizeAndPosition(); if (! isOnPath()) { QTransform m; m.translate(newOffset - oldOffset, 0.0); setTransformation(transformation() * m); } update(); notifyChanged(); } ArtisticTextShape::TextAnchor ArtisticTextShape::textAnchor() const { return m_textAnchor; } bool ArtisticTextShape::putOnPath(KoPathShape *path) { if (! path) { return false; } if (path->outline().isEmpty()) { return false; } if (! path->addDependee(this)) { return false; } update(); m_path = path; // use the paths outline converted to document coordinates as the baseline m_baseline = m_path->absoluteTransformation(0).map(m_path->outline()); // reset transformation setTransformation(QTransform()); updateSizeAndPosition(); // move to correct position setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeft); update(); return true; } bool ArtisticTextShape::putOnPath(const QPainterPath &path) { if (path.isEmpty()) { return false; } update(); if (m_path) { m_path->removeDependee(this); } m_path = 0; m_baseline = path; // reset transformation setTransformation(QTransform()); updateSizeAndPosition(); // move to correct position setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeft); update(); return true; } void ArtisticTextShape::removeFromPath() { update(); if (m_path) { m_path->removeDependee(this); } m_path = 0; m_baseline = QPainterPath(); updateSizeAndPosition(); update(); } bool ArtisticTextShape::isOnPath() const { return (m_path != 0 || ! m_baseline.isEmpty()); } ArtisticTextShape::LayoutMode ArtisticTextShape::layout() const { if (m_path) { return OnPathShape; } else if (! m_baseline.isEmpty()) { return OnPath; } else { return Straight; } } QPainterPath ArtisticTextShape::baseline() const { return m_baseline; } KoPathShape *ArtisticTextShape::baselineShape() const { return m_path; } QList ArtisticTextShape::removeText(int charIndex, int charCount) { QList extractedRanges; if (!charCount) { return extractedRanges; } if (charIndex == 0 && charCount >= plainText().length()) { beginTextUpdate(); extractedRanges = m_ranges; m_ranges.clear(); finishTextUpdate(); return extractedRanges; } CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || charPos.first >= m_ranges.count()) { return extractedRanges; } beginTextUpdate(); int extractedTextLength = 0; while (extractedTextLength < charCount) { ArtisticTextRange r = m_ranges[charPos.first].extract(charPos.second, charCount - extractedTextLength); extractedTextLength += r.text().length(); extractedRanges.append(r); if (extractedTextLength == charCount) { break; } charPos.first++; if (charPos.first >= m_ranges.count()) { break; } charPos.second = 0; } // now remove all empty ranges const int rangeCount = m_ranges.count(); for (int i = charPos.first; i < rangeCount; ++i) { if (m_ranges[charPos.first].text().isEmpty()) { m_ranges.removeAt(charPos.first); } } finishTextUpdate(); return extractedRanges; } QList ArtisticTextShape::copyText(int charIndex, int charCount) { QList extractedRanges; if (!charCount) { return extractedRanges; } CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || charPos.first >= m_ranges.count()) { return extractedRanges; } int extractedTextLength = 0; while (extractedTextLength < charCount) { ArtisticTextRange copy = m_ranges[charPos.first]; ArtisticTextRange r = copy.extract(charPos.second, charCount - extractedTextLength); extractedTextLength += r.text().length(); extractedRanges.append(r); if (extractedTextLength == charCount) { break; } charPos.first++; if (charPos.first >= m_ranges.count()) { break; } charPos.second = 0; } return extractedRanges; } void ArtisticTextShape::insertText(int charIndex, const QString &str) { if (isEmpty()) { appendText(str); return; } CharIndex charPos = indexOfChar(charIndex); if (charIndex < 0) { // insert before first character charPos = CharIndex(0, 0); } else if (charIndex >= plainText().length()) { // insert after last character charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length()); } // check range index, just in case if (charPos.first < 0) { return; } beginTextUpdate(); m_ranges[charPos.first].insertText(charPos.second, str); finishTextUpdate(); } void ArtisticTextShape::insertText(int charIndex, const ArtisticTextRange &textRange) { QList ranges; ranges.append(textRange); insertText(charIndex, ranges); } void ArtisticTextShape::insertText(int charIndex, const QList &textRanges) { if (isEmpty()) { beginTextUpdate(); m_ranges = textRanges; finishTextUpdate(); return; } CharIndex charPos = indexOfChar(charIndex); if (charIndex < 0) { // insert before first character charPos = CharIndex(0, 0); } else if (charIndex >= plainText().length()) { // insert after last character charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length()); } // check range index, just in case if (charPos.first < 0) { return; } beginTextUpdate(); ArtisticTextRange &hitRange = m_ranges[charPos.first]; if (charPos.second == 0) { // insert ranges before the hit range Q_FOREACH (const ArtisticTextRange &range, textRanges) { m_ranges.insert(charPos.first, range); charPos.first++; } } else if (charPos.second == hitRange.text().length()) { // insert ranges after the hit range Q_FOREACH (const ArtisticTextRange &range, textRanges) { m_ranges.insert(charPos.first + 1, range); charPos.first++; } } else { // insert ranges inside hit range ArtisticTextRange right = hitRange.extract(charPos.second, hitRange.text().length()); m_ranges.insert(charPos.first + 1, right); // now insert after the left part of hit range Q_FOREACH (const ArtisticTextRange &range, textRanges) { m_ranges.insert(charPos.first + 1, range); charPos.first++; } } // TODO: merge ranges with same style finishTextUpdate(); } void ArtisticTextShape::appendText(const QString &text) { beginTextUpdate(); if (isEmpty()) { m_ranges.append(ArtisticTextRange(text, defaultFont())); } else { m_ranges.last().appendText(text); } finishTextUpdate(); } void ArtisticTextShape::appendText(const ArtisticTextRange &text) { beginTextUpdate(); m_ranges.append(text); // TODO: merge ranges with same style finishTextUpdate(); } bool ArtisticTextShape::replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange) { QList ranges; ranges.append(textRange); return replaceText(charIndex, charCount, ranges); } bool ArtisticTextShape::replaceText(int charIndex, int charCount, const QList &textRanges) { CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || !charCount) { return false; } beginTextUpdate(); removeText(charIndex, charCount); insertText(charIndex, textRanges); finishTextUpdate(); return true; } qreal ArtisticTextShape::charAngleAt(int charIndex) const { if (isOnPath()) { qreal t = m_charOffsets.value(qBound(0, charIndex, m_charOffsets.size() - 1)); return m_baseline.angleAtPercent(t); } return 0.0; } QPointF ArtisticTextShape::charPositionAt(int charIndex) const { return m_charPositions.value(qBound(0, charIndex, m_charPositions.size() - 1)); } QRectF ArtisticTextShape::charExtentsAt(int charIndex) const { CharIndex charPos = indexOfChar(charIndex); if (charIndex < 0 || isEmpty()) { charPos = CharIndex(0, 0); } else if (charPos.first < 0) { charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length() - 1); } if (charPos.first < m_ranges.size()) { const ArtisticTextRange &range = m_ranges.at(charPos.first); QFontMetrics metrics(range.font()); int w = metrics.charWidth(range.text(), charPos.second); return QRectF(0, 0, w, metrics.height()); } return QRectF(); } void ArtisticTextShape::updateSizeAndPosition(bool global) { QTransform shapeTransform = absoluteTransformation(0); // determine baseline position in document coordinates QPointF oldBaselinePosition = shapeTransform.map(QPointF(0, baselineOffset())); createOutline(); QRectF bbox = m_outline.boundingRect(); if (bbox.isEmpty()) { bbox = nullBoundBox(); } if (isOnPath()) { // calculate the offset we have to apply to keep our position QPointF offset = m_outlineOrigin - bbox.topLeft(); // cache topleft corner of baseline path m_outlineOrigin = bbox.topLeft(); // the outline position is in document coordinates // so we adjust our position QTransform m; m.translate(-offset.x(), -offset.y()); global ? applyAbsoluteTransformation(m) : applyTransformation(m); } else { // determine the new baseline position in document coordinates QPointF newBaselinePosition = shapeTransform.map(QPointF(0, -bbox.top())); // apply a transformation to compensate any translation of // our baseline position QPointF delta = oldBaselinePosition - newBaselinePosition; QTransform m; m.translate(delta.x(), delta.y()); applyAbsoluteTransformation(m); } setSize(bbox.size()); // map outline to shape coordinate system QTransform normalizeMatrix; normalizeMatrix.translate(-bbox.left(), -bbox.top()); m_outline = normalizeMatrix.map(m_outline); const int charCount = m_charPositions.count(); for (int i = 0; i < charCount; ++i) { m_charPositions[i] = normalizeMatrix.map(m_charPositions[i]); } } void ArtisticTextShape::cacheGlyphOutlines() { m_charOutlines.clear(); Q_FOREACH (const ArtisticTextRange &range, m_ranges) { const QString rangeText = range.text(); const QFont rangeFont(range.font(), &m_paintDevice); const int textLength = rangeText.length(); for (int charIdx = 0; charIdx < textLength; ++charIdx) { QPainterPath charOutline; charOutline.addText(QPointF(), rangeFont, rangeText[charIdx]); m_charOutlines.append(charOutline); } } } void ArtisticTextShape::shapeChanged(ChangeType type, KoShape *shape) { if (m_path && shape == m_path) { if (type == KoShape::Deleted) { // baseline shape was deleted m_path = 0; } else if (type == KoShape::ParentChanged && !shape->parent()) { // baseline shape was probably removed from the document m_path->removeDependee(this); m_path = 0; } else { update(); // use the paths outline converted to document coordinates as the baseline m_baseline = m_path->absoluteTransformation(0).map(m_path->outline()); updateSizeAndPosition(true); update(); } } } CharIndex ArtisticTextShape::indexOfChar(int charIndex) const { if (isEmpty()) { return CharIndex(-1, -1); } int rangeIndex = 0; int textLength = 0; Q_FOREACH (const ArtisticTextRange &range, m_ranges) { const int rangeTextLength = range.text().length(); if (static_cast(charIndex) < textLength + rangeTextLength) { return CharIndex(rangeIndex, charIndex - textLength); } textLength += rangeTextLength; rangeIndex++; } return CharIndex(-1, -1); } void ArtisticTextShape::beginTextUpdate() { if (m_textUpdateCounter) { return; } m_textUpdateCounter++; update(); } void ArtisticTextShape::finishTextUpdate() { if (!m_textUpdateCounter) { return; } cacheGlyphOutlines(); updateSizeAndPosition(); update(); notifyChanged(); m_textUpdateCounter--; } bool ArtisticTextShape::saveSvg(SvgSavingContext &context) { context.shapeWriter().startElement("text", false); context.shapeWriter().addAttribute("id", context.getID(this)); SvgStyleWriter::saveSvgStyle(this, context); const QList formattedText = text(); // if we have only a single text range, save the font on the text element const bool hasSingleRange = formattedText.size() == 1; if (hasSingleRange) { saveSvgFont(formattedText.first().font(), context); } qreal anchorOffset = 0.0; if (textAnchor() == ArtisticTextShape::AnchorMiddle) { anchorOffset += 0.5 * this->size().width(); context.shapeWriter().addAttribute("text-anchor", "middle"); } else if (textAnchor() == ArtisticTextShape::AnchorEnd) { anchorOffset += this->size().width(); context.shapeWriter().addAttribute("text-anchor", "end"); } // check if we are set on a path if (layout() == ArtisticTextShape::Straight) { context.shapeWriter().addAttributePt("x", anchorOffset); context.shapeWriter().addAttributePt("y", baselineOffset()); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); Q_FOREACH (const ArtisticTextRange &range, formattedText) { saveSvgTextRange(range, context, !hasSingleRange, baselineOffset()); } } else { KoPathShape *baselineShape = KoPathShape::createShapeFromPainterPath(baseline()); QString id = context.createUID("baseline"); context.styleWriter().startElement("path"); context.styleWriter().addAttribute("id", id); context.styleWriter().addAttribute("d", baselineShape->toString(baselineShape->absoluteTransformation(0) * context.userSpaceTransform())); context.styleWriter().endElement(); context.shapeWriter().startElement("textPath"); context.shapeWriter().addAttribute("xlink:href", QLatin1Char('#') + id); if (startOffset() > 0.0) { context.shapeWriter().addAttribute("startOffset", QString("%1%").arg(startOffset() * 100.0)); } Q_FOREACH (const ArtisticTextRange &range, formattedText) { saveSvgTextRange(range, context, !hasSingleRange, baselineOffset()); } context.shapeWriter().endElement(); delete baselineShape; } context.shapeWriter().endElement(); return true; } void ArtisticTextShape::saveSvgFont(const QFont &font, SvgSavingContext &context) { context.shapeWriter().addAttribute("font-family", font.family()); context.shapeWriter().addAttributePt("font-size", font.pointSizeF()); if (font.bold()) { context.shapeWriter().addAttribute("font-weight", "bold"); } if (font.italic()) { context.shapeWriter().addAttribute("font-style", "italic"); } } void ArtisticTextShape::saveSvgTextRange(const ArtisticTextRange &range, SvgSavingContext &context, bool saveRangeFont, qreal baselineOffset) { context.shapeWriter().startElement("tspan", false); if (range.hasXOffsets()) { const char *attributeName = (range.xOffsetType() == ArtisticTextRange::AbsoluteOffset ? "x" : "dx"); QString attributeValue; int charIndex = 0; while (range.hasXOffset(charIndex)) { if (charIndex) { attributeValue += QLatin1Char(','); } attributeValue += QString("%1").arg(SvgUtil::toUserSpace(range.xOffset(charIndex++))); } context.shapeWriter().addAttribute(attributeName, attributeValue); } if (range.hasYOffsets()) { if (range.yOffsetType() != ArtisticTextRange::AbsoluteOffset) { baselineOffset = 0; } const char *attributeName = (range.yOffsetType() == ArtisticTextRange::AbsoluteOffset ? " y" : " dy"); QString attributeValue; int charIndex = 0; while (range.hasYOffset(charIndex)) { if (charIndex) { attributeValue += QLatin1Char(','); } attributeValue += QString("%1").arg(SvgUtil::toUserSpace(baselineOffset + range.yOffset(charIndex++))); } context.shapeWriter().addAttribute(attributeName, attributeValue); } if (range.hasRotations()) { QString attributeValue; int charIndex = 0; while (range.hasRotation(charIndex)) { if (charIndex) { attributeValue += ','; } attributeValue += QString("%1").arg(range.rotation(charIndex++)); } context.shapeWriter().addAttribute("rotate", attributeValue); } if (range.baselineShift() != ArtisticTextRange::None) { switch (range.baselineShift()) { case ArtisticTextRange::Sub: context.shapeWriter().addAttribute("baseline-shift", "sub"); break; case ArtisticTextRange::Super: context.shapeWriter().addAttribute("baseline-shift", "super"); break; case ArtisticTextRange::Percent: context.shapeWriter().addAttribute("baseline-shift", QString("%1%").arg(range.baselineShiftValue() * 100)); break; case ArtisticTextRange::Length: context.shapeWriter().addAttribute("baseline-shift", QString("%1%").arg(SvgUtil::toUserSpace(range.baselineShiftValue()))); break; default: break; } } if (saveRangeFont) { saveSvgFont(range.font(), context); } context.shapeWriter().addTextNode(range.text()); context.shapeWriter().endElement(); } bool ArtisticTextShape::loadSvg(const KoXmlElement &textElement, SvgLoadingContext &context) { clear(); QString anchor; if (!textElement.attribute("text-anchor").isEmpty()) { anchor = textElement.attribute("text-anchor"); } SvgStyles elementStyles = context.styleParser().collectStyles(textElement); context.styleParser().parseFont(elementStyles); ArtisticTextLoadingContext textContext; textContext.parseCharacterTransforms(textElement, context.currentGC()); KoXmlElement parentElement = textElement; // first check if we have a "textPath" child element for (KoXmlNode n = textElement.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement e = n.toElement(); if (e.tagName() == "textPath") { parentElement = e; break; } } KoPathShape *path = 0; bool pathInDocument = false; double offset = 0.0; const bool hasTextPathElement = parentElement != textElement && parentElement.hasAttribute("xlink:href"); if (hasTextPathElement) { // create the referenced path shape context.pushGraphicsContext(parentElement); context.styleParser().parseFont(context.styleParser().collectStyles(parentElement)); textContext.pushCharacterTransforms(); textContext.parseCharacterTransforms(parentElement, context.currentGC()); QString href = parentElement.attribute("xlink:href").mid(1); if (context.hasDefinition(href)) { const KoXmlElement &p = context.definition(href); // must be a path element as per svg spec if (p.tagName() == "path") { pathInDocument = false; path = new KoPathShape(); path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(p.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); path->applyAbsoluteTransformation(context.currentGC()->matrix); } } else { path = dynamic_cast(context.shapeById(href)); if (path) { pathInDocument = true; } } // parse the start offset if (! parentElement.attribute("startOffset").isEmpty()) { QString start = parentElement.attribute("startOffset"); if (start.endsWith('%')) { offset = 0.01 * start.remove('%').toDouble(); } else { const float pathLength = path ? path->outline().length() : 0.0; if (pathLength > 0.0) { offset = start.toDouble() / pathLength; } } } } if (parentElement.hasChildNodes()) { // parse child elements parseTextRanges(parentElement, context, textContext); if (!context.currentGC()->preserveWhitespace) { const QString text = plainText(); if (text.endsWith(' ')) { removeText(text.length() - 1, 1); } } setPosition(textContext.textPosition()); } else { // a single text range appendText(createTextRange(textElement.text(), textContext, context.currentGC())); setPosition(textContext.textPosition()); } if (hasTextPathElement) { if (path) { if (pathInDocument) { putOnPath(path); } else { putOnPath(path->absoluteTransformation(0).map(path->outline())); delete path; } if (offset > 0.0) { setStartOffset(offset); } } textContext.popCharacterTransforms(); context.popGraphicsContext(); } // adjust position by baseline offset if (! isOnPath()) { setPosition(position() - QPointF(0, baselineOffset())); } if (anchor == "middle") { setTextAnchor(ArtisticTextShape::AnchorMiddle); } else if (anchor == "end") { setTextAnchor(ArtisticTextShape::AnchorEnd); } return true; } void ArtisticTextShape::parseTextRanges(const KoXmlElement &element, SvgLoadingContext &context, ArtisticTextLoadingContext &textContext) { for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement e = n.toElement(); if (e.isNull()) { ArtisticTextRange range = createTextRange(n.toText().data(), textContext, context.currentGC()); appendText(range); } else if (e.tagName() == "tspan") { SvgGraphicsContext *gc = context.pushGraphicsContext(e); context.styleParser().parseFont(context.styleParser().collectStyles(e)); textContext.pushCharacterTransforms(); textContext.parseCharacterTransforms(e, gc); parseTextRanges(e, context, textContext); textContext.popCharacterTransforms(); context.popGraphicsContext(); } else if (e.tagName() == "tref") { if (e.attribute("xlink:href").isEmpty()) { continue; } QString href = e.attribute("xlink:href").mid(1); ArtisticTextShape *refText = dynamic_cast(context.shapeById(href)); if (refText) { foreach (const ArtisticTextRange &range, refText->text()) { appendText(range); } } else if (context.hasDefinition(href)) { const KoXmlElement &p = context.definition(href); SvgGraphicsContext *gc = context.currentGC(); appendText(ArtisticTextRange(textContext.simplifyText(p.text(), gc->preserveWhitespace), gc->font)); } } else { continue; } } } ArtisticTextRange ArtisticTextShape::createTextRange(const QString &text, ArtisticTextLoadingContext &context, SvgGraphicsContext *gc) { ArtisticTextRange range(context.simplifyText(text, gc->preserveWhitespace), gc->font); const int textLength = range.text().length(); switch (context.xOffsetType()) { case ArtisticTextLoadingContext::Absolute: range.setXOffsets(context.xOffsets(textLength), ArtisticTextRange::AbsoluteOffset); break; case ArtisticTextLoadingContext::Relative: range.setXOffsets(context.xOffsets(textLength), ArtisticTextRange::RelativeOffset); break; default: // no x-offsets break; } switch (context.yOffsetType()) { case ArtisticTextLoadingContext::Absolute: range.setYOffsets(context.yOffsets(textLength), ArtisticTextRange::AbsoluteOffset); break; case ArtisticTextLoadingContext::Relative: range.setYOffsets(context.yOffsets(textLength), ArtisticTextRange::RelativeOffset); break; default: // no y-offsets break; } range.setRotations(context.rotations(textLength)); #if 0 range.setLetterSpacing(gc->letterSpacing); range.setWordSpacing(gc->wordSpacing); if (gc->baselineShift == "sub") { range.setBaselineShift(ArtisticTextRange::Sub); } else if (gc->baselineShift == "super") { range.setBaselineShift(ArtisticTextRange::Super); } else if (gc->baselineShift.endsWith('%')) { range.setBaselineShift(ArtisticTextRange::Percent, SvgUtil::fromPercentage(gc->baselineShift)); } else { qreal value = SvgUtil::parseUnitX(gc, gc->baselineShift); if (value != 0.0) { range.setBaselineShift(ArtisticTextRange::Length, value); } } #endif //range.printDebug(); return range; } diff --git a/plugins/flake/artistictextshape/ArtisticTextShape.h b/plugins/flake/artistictextshape/ArtisticTextShape.h index 1dc03190a2..70070b0b3f 100644 --- a/plugins/flake/artistictextshape/ArtisticTextShape.h +++ b/plugins/flake/artistictextshape/ArtisticTextShape.h @@ -1,236 +1,237 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008,2011 Jan Hambrecht * Copyright (C) 2008 Rob Buis * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ARTISTICTEXTSHAPE_H #define ARTISTICTEXTSHAPE_H #include "ArtisticTextRange.h" #include #include #include #include #include #include class QPainter; class KoPathShape; class ArtisticTextLoadingContext; class SvgGraphicsContext; #define ArtisticTextShapeID "ArtisticText" /// Character position within text shape (range index, range character index) typedef QPair CharIndex; class ArtisticTextShape : public KoShape, public SvgShape { public: enum TextAnchor { AnchorStart, AnchorMiddle, AnchorEnd }; enum LayoutMode { Straight, ///< baseline is a straight line OnPath, ///< baseline is a QPainterPath OnPathShape ///< baseline is the outline of a path shape }; ArtisticTextShape(); ~ArtisticTextShape() override; + virtual KoShape *cloneShape() const; /// reimplemented void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override; /// reimplemented void saveOdf(KoShapeSavingContext &context) const override; /// reimplemented bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; /// reimplemented QSizeF size() const override; /// reimplemented void setSize(const QSizeF &size) override; /// reimplemented QPainterPath outline() const override; /// reimplemented from SvgShape bool saveSvg(SvgSavingContext &context) override; /// reimplemented from SvgShape bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override; /// Sets the plain text to display void setPlainText(const QString &newText); /// Returns the plain text content QString plainText() const; /// Returns formatted text QList text() const; /// Returns if text shape is empty, i.e. no text bool isEmpty() const; /// Clears the text shape void clear(); /** * Sets the font used for drawing * Note that it is expected that the font has its point size set * in postscript points. */ void setFont(const QFont &font); /** * Sets the font for the specified range of characters * @param charIndex the index of the first character of the range * @param charCount the number of characters of the range * @param font the new font to set */ void setFont(int charIndex, int charCount, const QFont &font); /** * Returns the font at the specified character position * If the text shape is empty it will return the default font. * If the character index is smaller than zero it will return the font * of the first character. If the character index is greater than the * last character index it will return the font of the last character. */ QFont fontAt(int charIndex) const; /// Returns the default font QFont defaultFont() const; /// Attaches this text shape to the given path shape bool putOnPath(KoPathShape *path); /// Puts the text on the given path, the path is expected to be in document coordinates bool putOnPath(const QPainterPath &path); /// Detaches this text shape from an already attached path shape void removeFromPath(); /// Returns if shape is attached to a path shape bool isOnPath() const; /// Sets the offset for for text on path void setStartOffset(qreal offset); /// Returns the start offset for text on path qreal startOffset() const; /** * Returns the y-offset from the top-left corner to the baseline. * This is usable for being able to exactly position the texts baseline. * Note: The value makes only sense for text not attached to a path. */ qreal baselineOffset() const; /// Sets the text anchor void setTextAnchor(TextAnchor anchor); /// Returns the actual text anchor TextAnchor textAnchor() const; /// Returns the current layout mode LayoutMode layout() const; /// Returns the baseline path QPainterPath baseline() const; /// Returns a pointer to the shape used as baseline KoPathShape *baselineShape() const; /// Removes a range of text starting from the given character QList removeText(int charIndex, int charCount); /// Copies a range of text starting from the given character QList copyText(int charIndex, int charCount); /// Adds a range of text at the given index void insertText(int charIndex, const QString &plainText); /// Adds range of text at the given index void insertText(int charIndex, const ArtisticTextRange &textRange); /// Adds ranges of text at the given index void insertText(int charIndex, const QList &textRanges); /// Appends plain text to the last text range void appendText(const QString &plainText); /// Appends a single formatted range of text void appendText(const ArtisticTextRange &text); /// Replaces a range of text with the specified text range bool replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange); /// Replaces a range of text with the specified text ranges bool replaceText(int charIndex, int charCount, const QList &textRanges); /// Gets the angle of the char with the given index qreal charAngleAt(int charIndex) const; /// Gets the position of the char with the given index in shape coordinates QPointF charPositionAt(int charIndex) const; /// Gets the extents of the char with the given index QRectF charExtentsAt(int charIndex) const; /// Returns index of range and index within range of specified character CharIndex indexOfChar(int charIndex) const; /// reimplemented from KoShape void shapeChanged(ChangeType type, KoShape *shape) override; private: void updateSizeAndPosition(bool global = false); void cacheGlyphOutlines(); bool pathHasChanged() const; void createOutline(); void beginTextUpdate(); void finishTextUpdate(); /// Calculates abstract character positions in baseline coordinates QVector calculateAbstractCharacterPositions(); /// Returns the bounding box for an empty text shape QRectF nullBoundBox() const; /// Saves svg font void saveSvgFont(const QFont &font, SvgSavingContext &context); /// Saves svg text range void saveSvgTextRange(const ArtisticTextRange &range, SvgSavingContext &context, bool saveFont, qreal baselineOffset); /// Parse nested text ranges void parseTextRanges(const KoXmlElement &element, SvgLoadingContext &context, ArtisticTextLoadingContext &textContext); /// Creates text range ArtisticTextRange createTextRange(const QString &text, ArtisticTextLoadingContext &context, SvgGraphicsContext *gc); QList m_ranges; KoPostscriptPaintDevice m_paintDevice; KoPathShape *m_path; ///< the path shape we are attached to QList m_charOutlines; ///< cached character oulines qreal m_startOffset; ///< the offset from the attached path start point QPointF m_outlineOrigin; ///< the top-left corner of the non-normalized text outline QPainterPath m_outline; ///< the actual text outline QPainterPath m_baseline; ///< the baseline path the text is put on TextAnchor m_textAnchor; ///< the actual text anchor QVector m_charOffsets; ///< char positions [0..1] on baseline path QVector m_charPositions; ///< char positions in shape coordinates int m_textUpdateCounter; QFont m_defaultFont; }; #endif // ARTISTICTEXTSHAPE_H diff --git a/plugins/impex/brush/kis_brush_export.cpp b/plugins/impex/brush/kis_brush_export.cpp index 9b75cfe699..1a4a196955 100644 --- a/plugins/impex/brush/kis_brush_export.cpp +++ b/plugins/impex/brush/kis_brush_export.cpp @@ -1,224 +1,226 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_brush_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct KisBrushExportOptions { qreal spacing; bool mask; int brushStyle; int selectionMode; QString name; }; K_PLUGIN_FACTORY_WITH_JSON(KisBrushExportFactory, "krita_brush_export.json", registerPlugin();) KisBrushExport::KisBrushExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushExport::~KisBrushExport() { } KisImportExportFilter::ConversionStatus KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { // XXX: Loading the parasite itself was commented out -- needs investigation // KisAnnotationSP annotation = document->savingImage()->annotation("ImagePipe Parasite"); // KisPipeBrushParasite parasite; // if (annotation) { // QBuffer buf(const_cast(&annotation->annotation())); // buf.open(QBuffer::ReadOnly); // parasite.loadFromDevice(&buf); // buf.close(); // } KisBrushExportOptions exportOptions; if (document->savingImage()->dynamicPropertyNames().contains("brushspacing")) { exportOptions.spacing = document->savingImage()->property("brushspacing").toFloat(); } else { exportOptions.spacing = configuration->getInt("spacing"); } if (!configuration->getString("name").isEmpty()) { exportOptions.name = configuration->getString("name"); } else { exportOptions.name = document->savingImage()->objectName(); } exportOptions.mask = configuration->getBool("mask"); exportOptions.selectionMode = configuration->getInt("selectionMode"); exportOptions.brushStyle = configuration->getInt("brushStyle"); KisGbrBrush *brush = 0; if (mimeType() == "image/x-gimp-brush") { brush = new KisGbrBrush(filename()); } else if (mimeType() == "image/x-gimp-brush-animated") { brush = new KisImagePipeBrush(filename()); } else { return KisImportExportFilter::BadMimeType; } qApp->processEvents(); // For vector layers to be updated QRect rc = document->savingImage()->bounds(); brush->setName(exportOptions.name); brush->setSpacing(exportOptions.spacing); brush->setUseColorAsMask(exportOptions.mask); int w = document->savingImage()->width(); int h = document->savingImage()->height(); KisImagePipeBrush *pipeBrush = dynamic_cast(brush); if (pipeBrush) { // Create parasite. XXX: share with KisCustomBrushWidget QVector< QVector > devices; devices.push_back(QVector()); KoProperties properties; properties.setProperty("visible", true); QList layers = document->savingImage()->root()->childNodes(QStringList("KisLayer"), properties); Q_FOREACH (KisNodeSP node, layers) { devices[0].push_back(node->projection().data()); } QVector modes; switch (exportOptions.selectionMode) { case 0: modes.push_back(KisParasite::Constant); break; case 1: modes.push_back(KisParasite::Random); break; case 2: modes.push_back(KisParasite::Incremental); break; case 3: modes.push_back(KisParasite::Pressure); break; case 4: modes.push_back(KisParasite::Angular); break; default: modes.push_back(KisParasite::Incremental); } KisPipeBrushParasite parasite; // XXX: share code with KisImagePipeBrush, when we figure out how to support more gih features parasite.dim = devices.count(); // XXX Change for multidim! : parasite.ncells = devices.at(0).count(); parasite.rank[0] = parasite.ncells; // ### This can mask some bugs, be careful here in the future parasite.selection[0] = modes.at(0); // XXX needsmovement! parasite.setBrushesCount(); pipeBrush->setParasite(parasite); pipeBrush->setDevices(devices, w, h); } else { QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); brush->setImage(image); } brush->setWidth(w); brush->setHeight(h); - brush->saveToDevice(io); - - return KisImportExportFilter::OK; + if (brush->saveToDevice(io)) { + return KisImportExportFilter::OK; + } + else { + return KisImportExportFilter::CreationError; + } } KisPropertiesConfigurationSP KisBrushExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", 1.0); cfg->setProperty("name", ""); cfg->setProperty("mask", true); cfg->setProperty("selectionMode", 0); cfg->setProperty("brushStyle", 0); return cfg; } KisConfigWidget *KisBrushExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &to) const { KisWdgOptionsBrush *wdg = new KisWdgOptionsBrush(parent); if (to == "image/x-gimp-brush") { wdg->groupBox->setVisible(false); } else if (to == "image/x-gimp-brush-animated") { wdg->groupBox->setVisible(true); } return wdg; } void KisBrushExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Gimp Brushes"); if (mimeType() == "image/x-gimp-brush-animated") { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); } } void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg) { spacingWidget->setSpacing(false, cfg->getBool("spacing")); nameLineEdit->setText(cfg->getString("name")); - brushNameLbl->setText(cfg->getString("name")); colorAsMask->setChecked(cfg->getBool("mask")); brushStyle->setCurrentIndex(cfg->getInt("selectionMode")); cmbSelectionMode->setCurrentIndex(cfg->getInt("brushStyle")); } KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", spacingWidget->spacing()); - cfg->setProperty("name", brushNameLbl->text()); + cfg->setProperty("name", nameLineEdit->text()); cfg->setProperty("mask", colorAsMask->isChecked()); cfg->setProperty("selectionMode", brushStyle->currentIndex()); cfg->setProperty("brushStyle", cmbSelectionMode->currentIndex()); return cfg; } #include "kis_brush_export.moc" diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h index ba0c4077ed..ca6757434c 100644 --- a/plugins/impex/brush/kis_brush_export.h +++ b/plugins/impex/brush/kis_brush_export.h @@ -1,60 +1,59 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_Brush_EXPORT_H_ #define _KIS_Brush_EXPORT_H_ #include #include #include #include #include class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih { Q_OBJECT public: KisWdgOptionsBrush(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; }; class KisBrushExport : public KisImportExportFilter { Q_OBJECT public: KisBrushExport(QObject *parent, const QVariantList &); ~KisBrushExport() override; - bool supportsIO() const override { return false; } KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp index aaf810a618..6e3ebb7ebf 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp @@ -1,253 +1,253 @@ /* * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_tangent_normal_paintop.h" #include #include #include #include #include #include #include #include #include #include #include #include KisTangentNormalPaintOp::KisTangentNormalPaintOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image): KisBrushBasedPaintOp(settings, painter), m_opacityOption(node), m_tempDev(painter->device()->createCompositionSourceDevice()) { Q_UNUSED(image); //Init, read settings, etc// m_tangentTiltOption.readOptionSetting(settings); m_airbrushOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisTangentNormalPaintOp::~KisTangentNormalPaintOp() { //destroy things here// } KisSpacingInformation KisTangentNormalPaintOp::paintAt(const KisPaintInformation& info) { /* * For the color, the precision of tilt is only 60x60, and the precision of direction and rotation are 360 and 360*90. * You can't get more precise than 8bit. Therefore, we will check if the current space is RGB, * if so we request a profile with that space and 8bit bit depth, if not, just sRGB */ KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { - rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); + rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { - rgbColorSpace = currentColor.colorSpace(); + rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace);//Should be default RGB(0.5,0.5,1.0) //draw stuff here, return kisspacinginformation. KisBrushSP brush = m_brush; if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF cursorPos = - m_scatterOption.apply(info, - brush->maskWidth(shape, 0, 0, info), - brush->maskHeight(shape, 0, 0, info)); + m_scatterOption.apply(info, + brush->maskWidth(shape, 0, 0, info), + brush->maskHeight(shape, 0, 0, info)); m_maskDab = - m_dabCache->fetchDab(rgbColorSpace, color, cursorPos, - shape, - info, m_softnessOption.apply(info), - &m_dstDabRect); + m_dabCache->fetchDab(rgbColorSpace, color, cursorPos, + shape, + info, m_softnessOption.apply(info), + &m_dstDabRect); if (m_dstDabRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = m_maskDab->bounds(); // sanity check Q_ASSERT(m_dstDabRect.size() == dabRect.size()); Q_UNUSED(dabRect); quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); //paint with the default color? Copied this from color smudge.// //painter()->setCompositeOp(COMPOSITE_COPY); //painter()->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); painter()->bltFixed(m_dstDabRect.topLeft(), m_maskDab, m_maskDab->bounds()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacity and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); return computeSpacing(info, scale, rotation); } KisSpacingInformation KisTangentNormalPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); return computeSpacing(info, scale, rotation); } KisTimingInformation KisTangentNormalPaintOp::updateTimingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } KisSpacingInformation KisTangentNormalPaintOp::computeSpacing(const KisPaintInformation &info, qreal scale, qreal rotation) const { return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); } void KisTangentNormalPaintOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = m_tempDev; } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); - KoColor currentColor = painter()->paintColor(); - QString currentSpace = currentColor.colorSpace()->colorModelId().id(); - const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); - if (currentSpace != "RGBA") { - rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); - } else { - rgbColorSpace = currentColor.colorSpace(); - } - QVector channelValues(4); - qreal r, g, b; + KoColor currentColor = painter()->paintColor(); + QString currentSpace = currentColor.colorSpace()->colorModelId().id(); + const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); + if (currentSpace != "RGBA") { + rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); + } else { + rgbColorSpace = currentColor.colorSpace(); + } + QVector channelValues(4); + qreal r, g, b; - if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ - channelValues[0] = 0.5;//red - channelValues[1] = 0.5;//green - channelValues[2] = 1.0;//blue - channelValues[3] = 1.0;//alpha, leave alone. + if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ + channelValues[0] = 0.5;//red + channelValues[1] = 0.5;//green + channelValues[2] = 1.0;//blue + channelValues[3] = 1.0;//alpha, leave alone. - m_tangentTiltOption.apply(pi2, &r, &g, &b); + m_tangentTiltOption.apply(pi2, &r, &g, &b); - channelValues[0] = r;//red - channelValues[1] = g;//green - channelValues[2] = b;//blue - } else { - channelValues[0] = 1.0;//blue - channelValues[1] = 0.5;//green - channelValues[2] = 0.5;//red - channelValues[3] = 1.0;//alpha, leave alone. + channelValues[0] = r;//red + channelValues[1] = g;//green + channelValues[2] = b;//blue + } else { + channelValues[0] = 1.0;//blue + channelValues[1] = 0.5;//green + channelValues[2] = 0.5;//red + channelValues[3] = 1.0;//alpha, leave alone. - m_tangentTiltOption.apply(pi2, &r, &g, &b); + m_tangentTiltOption.apply(pi2, &r, &g, &b); - channelValues[0] = b;//blue - channelValues[1] = g;//green - channelValues[2] = r;//red - } + channelValues[0] = b;//blue + channelValues[1] = g;//green + channelValues[2] = r;//red + } - quint8 data[4]; - rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); - KoColor color(data, rgbColorSpace); + quint8 data[4]; + rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); + KoColor color(data, rgbColorSpace); p.setPaintColor(color); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); - painter()->renderMirrorMask(rc, m_lineCacheDevice); + painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } }