diff --git a/.gitignore b/.gitignore index 4e87106445..d814e73cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,32 @@ *.orig __pycache__ *.egg-info *.trace build qtcreator-build *.kdev4 *~ .kateconfig CMakeLists.txt.user* .directory *.autosave *.swp .gdb_history .kdev_include_paths *.config *.creator *.creator.user *.files *.includes .DS_Store *.kate-swap .idea GTAGS GPATH GRTAGS GSYMS BROWSE *.kate-swp /po/ +build_dir +.flatpak-builder diff --git a/3rdparty/ext_frameworks/CMakeLists.txt b/3rdparty/ext_frameworks/CMakeLists.txt index 5e5354204e..67311172af 100644 --- a/3rdparty/ext_frameworks/CMakeLists.txt +++ b/3rdparty/ext_frameworks/CMakeLists.txt @@ -1,242 +1,243 @@ SET(EXTPREFIX_frameworks "${EXTPREFIX}" ) # # All needed frameworks: # # Archive # Config # WidgetsAddons # Completion # CoreAddons # GuiAddons # I18n # ItemModels # ItemViews # WindowSystem +# kimageformats # On Linux: # KCrash ExternalProject_Add( ext_extra_cmake_modules DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/extra-cmake-modules-5.44.0.zip URL_MD5 74aa8fc501e27024390b01c81f2925eb PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ecm_install_to_share.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" ) ExternalProject_Add( ext_karchive DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/karchive-5.44.0.zip URL_MD5 c60a8e22b88cc7328610041638459689 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/karchive.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_extra_cmake_modules ) ExternalProject_Add( ext_kconfig DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kconfig-5.44.0.zip URL_MD5 d0223ea471bbf463ec42c2a2355a5183 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kconfig.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_karchive ) ExternalProject_Add( ext_kwidgetsaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kwidgetsaddons-5.44.0.zip URL_MD5 a9911d8d0f8aaf7a7afd84c41c8f80a1 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwidgetsaddons.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kconfig ) ExternalProject_Add( ext_kcompletion DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kcompletion-5.44.0.zip URL_MD5 0647885a702c338a1b656eb4f311ad16 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwidgetsaddons ) ExternalProject_Add( ext_kcoreaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kcoreaddons-5.44.0.zip URL_MD5 16a7379f3e2941d1c19d6f80939f15e8 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/desktoptojson.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcompletion ) ExternalProject_Add( ext_kguiaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kguiaddons-5.44.0.zip URL_MD5 440eefbf5abcafc492dcf857f7e4eaf5 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcoreaddons ) if(APPLE) ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/ki18n-5.44.0.zip URL_MD5 333ab0a3f65a298e928d746144d4dc8e INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ext_gettext ) else() ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/ki18n-5.44.0.zip URL_MD5 333ab0a3f65a298e928d746144d4dc8e INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ) endif() ExternalProject_Add( ext_kitemmodels DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kitemmodels-5.44.0.zip URL_MD5 ea43a5e2cc7033eb672796b108d7403b INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_ki18n ) ExternalProject_Add( ext_kitemviews DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kitemviews-5.44.0.zip URL_MD5 8b15c703313c7a790c7db897ef17de7d INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemmodels ) ExternalProject_Add( ext_kimageformats DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kimageformats-5.44.0.zip URL_MD5 02a98b682f9cb655592148d7ebcc05e7 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kimageformats.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemviews ) ExternalProject_Add( ext_kwindowsystem DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kwindowsystem-5.44.0.zip URL_MD5 75329f47cf8cd413fa1d15a57c298563 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwindowsystem-x11.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kimageformats ) ExternalProject_Add( ext_kcrash DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://download.kde.org/stable/frameworks/5.44/kcrash-5.44.0.zip URL_MD5 61adc0e125c65288968d958acf25f4aa INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwindowsystem ) diff --git a/CMakeLists.txt b/CMakeLists.txt index dae44e493f..3637b1f68c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,841 +1,843 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.18.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.11 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) - add_definitions(-Wno-suggest-override) + add_definitions(-Wno-suggest-override -Wextra) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.3.0-prealpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 3) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING + -DQT_USE_FAST_CONCATENATION + -DQT_USE_FAST_OPERATOR_PLUS ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) ########################### ############################ ## Required dependencies ## ############################ ########################### find_package(PNG REQUIRED) if (APPLE) # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() diff --git a/README.md b/README.md index 756a660104..4c87ca2901 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,51 @@ ![Picture](https://krita.org/wp-content/uploads/2019/04/krita-logo-2019.png) | Jenkins CI Name | Master | Stable | | --------------- | ------ | ------ | -| OpenSuse Qt 5.10 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.10/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.10/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.10)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.10/)| -| FreeBSD Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.12/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.12/)| +| OpenSuse Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.12)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.12/)| +| FreeBSD Qt 5.13 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/)| Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry. If you are reading this on Github, be aware that this is just a mirror. Our real code repository is provided by KDE: https://invent.kde.org/kde/krita ![Picture](https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg) ### User Manual https://docs.krita.org/en/user_manual.html ### Development Notes and Build Instructions If you're building on Windows or OSX you'll need to build some third-party dependencies first. You should look at the README in the 3rdparty folder for directions. If you're building on Linux, please follow David Revoy's Cat Guide: http://www.davidrevoy.com/article193/guide-building-krita-on-linux-for-cats Other developer guides, notes and wiki: https://docs.krita.org/en/untranslatable_pages.html Apidox: https://api.kde.org/extragear-api/graphics-apidocs/krita/html/index.html ### Bugs and Wishes https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1315444&product=krita&query_format=advanced ### Discussion Forum http://forum.kde.org/viewforum.php?f=136 ### IRC channel Most of the developers hang out here. If you are interested in helping with the project this is a great place to start. Many of the developers based in Europe so they may be offline depending on when you join. irc.freenode.net, #krita ### Project Website http://www.krita.org ### License Krita as a whole is licensed under the GNU Public License, Version 3. Individual files may have a different, but compatible license. diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index b65d701876..fb543193e3 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,818 +1,818 @@ @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 --cmd Launch a cmd prompt instead of building. echo The environment is set up like the build echo environment with some helper command macros. 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= set ARG_CMD= :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" == "--cmd" ( set ARG_CMD=1 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 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%" ) 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 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\" ) ) ) 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! ) :: Prepare the CMake command lines set CMDLINE_CMAKE_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" ^ -DUSE_QT_TABLET_WINDOWS=ON ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% set CMDLINE_CMAKE_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 ^ -DFOUNDATION_BUILD=ON ^ -DUSE_QT_TABLET_WINDOWS=ON ^ - -DHIDE_SAFE_ASSERTS=ON ^ + -DHIDE_SAFE_ASSERTS=OFF ^ -Wno-dev ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% :: Launch CMD prompt if requested if "%ARG_CMD%" == "1" ( doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%" doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%" doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*" doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%" echo. title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^) echo You're now in the build environment. echo The following macros are available: echo cmake-deps echo -- Run CMake for the deps. echo make-deps ^ echo -- Run build for the specified deps target. The target name should echo include the `ext_` prefix, e.g. `ext_qt`. echo cmake-krita echo -- Run CMake for Krita. echo make-krita echo -- Run build for Krita's `install` target. echo. echo For more info, type `doskey /macros` to view the macro commands. cmd /k exit ) 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... @echo on %CMDLINE_CMAKE_DEPS% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost exiv2 fftw3 eigen3 set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt set EXT_TARGETS=%EXT_TARGETS% quazip for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... @echo on %CMDLINE_CMAKE_KRITA% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off 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/interfaces/KoID.h b/interfaces/KoID.h index 267fb0bd15..18d233ec38 100644 --- a/interfaces/KoID.h +++ b/interfaces/KoID.h @@ -1,136 +1,145 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2006 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 _KO_ID_H_ #define _KO_ID_H_ #include #include #include #include /** * A KoID is a combination of a user-visible string and a string that uniquely * identifies a given resource across languages. */ class KoID { public: KoID() : m_id() , m_name() {} /** * Construct a KoID with the given id, and name, id is the untranslated * official name of the id, name should be translatable as it will be used * in the UI. * * @code * KoID("id", i18n("name")) * @endcode */ explicit KoID(const QString &id, const QString &name = QString()) : m_id(id) , m_name(name) {} /** * Use this constructore for static KoID. as KoID("id", ki18n("name")); * the name will be translated the first time it is needed. This is * important because static objects are constructed before translations * are initialized. */ explicit KoID(const QString &id, const KLocalizedString &name) : m_id(id) , m_localizedString(name) {} KoID(const KoID &rhs) { m_id = rhs.m_id; m_name = rhs.name(); } + KoID &operator=(const KoID &rhs) + { + if (this != &rhs) { + m_id = rhs.m_id; + m_name = rhs.name(); + } + return *this; + } + QString id() const { return m_id; } QString name() const { if (m_name.isEmpty() && !m_localizedString.isEmpty()) { m_name = m_localizedString.toString(); } return m_name; } friend inline bool operator==(const KoID &, const KoID &); friend inline bool operator!=(const KoID &, const KoID &); friend inline bool operator<(const KoID &, const KoID &); friend inline bool operator>(const KoID &, const KoID &); static bool compareNames(const KoID &id1, const KoID &id2) { return id1.name() < id2.name(); } private: QString m_id; mutable QString m_name; KLocalizedString m_localizedString; }; Q_DECLARE_METATYPE(KoID) inline bool operator==(const KoID &v1, const KoID &v2) { return v1.m_id == v2.m_id; } inline bool operator!=(const KoID &v1, const KoID &v2) { return v1.m_id != v2.m_id; } inline bool operator<(const KoID &v1, const KoID &v2) { return v1.m_id < v2.m_id; } inline bool operator>(const KoID &v1, const KoID &v2) { return v1.m_id > v2.m_id; } inline QDebug operator<<(QDebug dbg, const KoID &id) { dbg.nospace() << id.name() << " (" << id.id() << " )"; return dbg.space(); } #endif diff --git a/krita/data/aboutdata/LICENSE b/krita/data/aboutdata/LICENSE index 2e6a0b73d4..94a9ed024d 100644 --- a/krita/data/aboutdata/LICENSE +++ b/krita/data/aboutdata/LICENSE @@ -1,340 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of this License. - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least +state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Copyright (C) 19yy + Copyright (C) - This program is free software; you can redistribute it and/or modify + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) 19yy name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/krita/data/kritarc b/krita/data/kritarc index 477968b406..e7a30c1b95 100644 --- a/krita/data/kritarc +++ b/krita/data/kritarc @@ -1,371 +1,368 @@ [advancedColorSelector] allowHorizontalLayout=true colorSelectorConfiguration=3|0|5|0 commonColorsAlignment=false commonColorsAutoUpdate=false commonColorsCount=12 commonColorsHeight=16 commonColorsNumCols=1 commonColorsNumRows=1 commonColorsScrolling=true commonColorsShow=true commonColorsWidth=16 customColorSpaceDepthID=U8 customColorSpaceModel=RGBA customColorSpaceProfile=sRGB built-in lastUsedColorsAlignment=true lastUsedColorsCount=20 lastUsedColorsHeight=16 lastUsedColorsNumCols=1 lastUsedColorsNumRows=1 lastUsedColorsScrolling=true lastUsedColorsShow=true lastUsedColorsWidth=16 minimalShadeSelectorAsGradient=true minimalShadeSelectorLineConfig=0|0.2|0|0|0|0|0;1|0|1|1|0|0|0;2|0|-1|1|0|0|0; minimalShadeSelectorLineHeight=10 minimalShadeSelectorPatchCount=10 popupOnMouseClick=true popupOnMouseOver=false shadeSelectorHideable=false shadeSelectorType=Minimal shadeSelectorUpdateOnBackground=true shadeSelectorUpdateOnForeground=true shadeSelectorUpdateOnLeftClick=false shadeSelectorUpdateOnRightClick=false useCustomColorSpace=false zoomSize=280 [DockWidget sharedtooldocker] TabbedMode=false [KisToolTransform] filterId=Bicubic [MainWindow] State=AAAA/wAAAAD9AAAABAAAAAAAAABJAAADzfwCAAAAA/sAAAAOAFQAbwBvAGwAQgBvAHgBAAAAPwAAA80AAAAxAP////sAAAAkAEYAbABvAHcAUwBoAGEAcABlAEIAbwB4AEQAbwBjAGsAZQByAAAAA2oAAADHAAAAAAAAAAD7AAAAKABGAGwAbwB3AFMAdABlAG4AYwBpAGwAQgBvAHgARABvAGMAawBlAHIAAAADfQAAAMcAAAAAAAAAAAAAAAEAAAEGAAADzfwCAAAAQPsAAAAaAEsAaQBzAEIAaQByAGQAZQB5AGUAQgBvAHgAAAAAAP////8AAAAAAAAAAPsAAAAgAEsAaQBzAFAAYQBsAGUAdAB0AGUARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAaAEsAbwBDAG8AbABvAHIARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAwAEsAaQBzAFQAcgBpAGEAbgBnAGwAZQBDAG8AbABvAHIAUwBlAGwAZQBjAHQAbwByAAAAAAD/////AAAAAAAAAAD7AAAAIgBTAGgAYQBkAG8AdwAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAAgAFMAaABhAHAAZQAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAAaAFMAaABhAHAAZQBTAGUAbABlAGMAdABvAHIAAAAASAAAAEQAAAAAAAAAAPsAAAAkAFMAaQBtAHAAbABlACAAVABlAHgAdAAgAEUAZABpAHQAbwByAAAAAAD/////AAAAAAAAAAD8AAAAPwAAAOIAAACEAQAAHfoAAAAAAQAAAAf7AAAAHgBDAG8AbABvAHIAUwBlAGwAZQBjAHQAbwByAE4AZwEAAAAA/////wAAADoA////+wAAACAAcwBoAGEAcgBlAGQAdABvAG8AbABkAG8AYwBrAGUAcgEAAAAA/////wAAAFMA////+wAAABwATwB2AGUAcgB2AGkAZQB3AEQAbwBjAGsAZQByAQAAAAD/////AAAA2gD////7AAAAKgBTAHAAZQBjAGkAZgBpAGMAQwBvAGwAbwByAFMAZQBsAGUAYwB0AG8AcgAAAAAA/////wAAAL4A////+wAAABYAQwBvAGwAbwByAFMAbABpAGQAZQByAAAAAAD/////AAAAkQD////7AAAAFgBJAG0AYQBnAGUARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAqAFMAaABhAHAAZQBDAG8AbABsAGUAYwB0AGkAbwBuAEQAbwBjAGsAZQByAAAABkgAAAEoAAAAAAAAAAD7AAAARgBLAHIAaQB0AGEAUwBoAGEAcABlAC8ASwBpAHMAVABvAG8AbABEAHkAbgBhAG8AcAB0AGkAbwBuACAAdwBpAGQAZwBlAHQBAAAAUgAAABIAAAAAAAAAAPsAAAAsAEsAcgBpAHQAYQBTAGgAYQBwAGUALwBLAGkAcwBUAG8AbwBsAEwAaQBuAGUBAAAAPAAAAGkAAAAAAAAAAPsAAAAyAEsAcgBpAHQAYQBTAGgAYQBwAGUALwBLAGkAcwBUAG8AbwBsAEUAbABsAGkAcABzAGUBAAAAkQAAABIAAAAAAAAAAPsAAAAcAEsAaQBzAFQAbwBvAGwAUABvAGwAeQBnAG8AbgEAAACmAAAAEgAAAAAAAAAA+wAAAB4ASwBpAHMAVABvAG8AbABQAG8AbAB5AGwAaQBuAGUBAAAAuwAAABIAAAAAAAAAAPsAAAAWAEsAaQBzAFQAbwBvAGwAUwB0AGEAcgEAAADQAAAAEwAAAAAAAAAA+wAAACoAUwBuAGEAcABHAHUAaQBkAGUAQwBvAG4AZgBpAGcAVwBpAGQAZwBlAHQAAAAA7wAAAHEAAAAAAAAAAPsAAAAyAEsAaQBzAFQAbwBvAGwAQwByAG8AcAAgAG8AcAB0AGkAbwBuACAAdwBpAGQAZwBlAHQBAAAA+wAAABIAAAAAAAAAAPsAAABQAEsAcgBpAHQAYQBUAHIAYQBuAHMAZgBvAHIAbQAvAEsAaQBzAFQAbwBvAGwATQBvAHYAZQAgAE8AcAB0AGkAbwBuACAAVwBpAGQAZwBlAHQBAAABEAAAABIAAAAAAAAAAPsAAAA8AEsAaQBzAFQAbwBvAGwAVAByAGEAbgBzAGYAbwByAG0AIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAADwAAAAvAAAAAAAAAAD7AAAATgBLAHIAaQB0AGEAUwBoAGEAcABlAC8ASwBpAHMAVABvAG8AbABNAGUAYQBzAHUAcgBlACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAA8AAAAQgAAAAAAAAAA+wAAAFwASwByAGkAdABhAFMAZQBsAGUAYwB0AGUAZAAvAEsAaQBzAFQAbwBvAGwAQwBvAGwAbwByAFAAaQBjAGsAZQByACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAA8AAAA/wAAAAAAAAAA+wAAAEYASwBpAHMAUgB1AGwAZQByAEEAcwBzAGkAcwB0AGEAbgB0AFQAbwBvAGwAIABPAHAAdABpAG8AbgAgAFcAaQBkAGcAZQB0AQAAADwAAAASAAAAAAAAAAD7AAAASABLAGkAcwBUAG8AbwBsAFAAZQByAHMAcABlAGMAdABpAHYAZQBHAHIAaQBkACAATwBwAHQAaQBvAG4AIABXAGkAZABnAGUAdAEAAAGjAAAAEgAAAAAAAAAA+wAAADIASwBpAHMAVABvAG8AbABHAHIAaQBkACAATwBwAHQAaQBvAG4AIABXAGkAZABnAGUAdAEAAAG4AAAAEwAAAAAAAAAA+wAAAEwASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABSAGUAYwB0AGEAbgBnAHUAbABhAHIAIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAAc4AAAASAAAAAAAAAAD7AAAASgBLAGkAcwBUAG8AbwBsAFMAZQBsAGUAYwB0AEUAbABsAGkAcAB0AGkAYwBhAGwAIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAAeMAAAASAAAAAAAAAAD7AAAASABLAGkAcwBUAG8AbwBsAFMAZQBsAGUAYwB0AFAAbwBsAHkAZwBvAG4AYQBsACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAH4AAAAEgAAAAAAAAAA+wAAAEQASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABPAHUAdABsAGkAbgBlACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAINAAAAEgAAAAAAAAAA+wAAAEoASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABDAG8AbgB0AGkAZwB1AG8AdQBzACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAIiAAAAEgAAAAAAAAAA+wAAAEQASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABTAGkAbQBpAGwAYQByACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAI3AAAAEgAAAAAAAAAA/AAAAbYAAABaAAAAAAD////6AAAAAAEAAAAC+wAAAC4ASwBvAFMAaABhAHAAZQBDAG8AbABsAGUAYwB0AGkAbwBuAEQAbwBjAGsAZQByAQAAAAD/////AAAAAAAAAAD7AAAAJABTAG0AYQBsAGwAQwBvAGwAbwByAFMAZQBsAGUAYwB0AG8AcgAAAANuAAABBAAAADoA/////AAAASgAAAEwAAAA1QEAAB36AAAAAAEAAAAD+wAAABYASwBpAHMATABhAHkAZQByAEIAbwB4AQAAAAD/////AAABAgD////7AAAAGgBDAGgAYQBuAG4AZQBsAEQAbwBjAGsAZQByAQAAAAD/////AAAAVQD////7AAAALgBLAGkAcwBQAGEAaQBuAHQAZQByAGwAeQBNAGkAeABlAHIARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPwAAAJfAAABrQAAAJcBAAAd+gAAAAABAAAAAvsAAAAYAFAAcgBlAHMAZQB0AEQAbwBjAGsAZQByAQAAAAD/////AAAAZgD////7AAAAGgBQAHIAZQBzAGUAdABIAGkAcwB0AG8AcgB5AQAACPoAAAEGAAAAVQD////7AAAASABLAHIAaQB0AGEAUwBoAGEAcABlAC8ASwBpAHMAVABvAG8AbABCAHIAdQBzAGgAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAPcAAAAaAAAAAAAAAAA+wAAACIAUwB0AHIAbwBrAGUAIABQAHIAbwBwAGUAcgB0AGkAZQBzAAAAAAD/////AAAAAAAAAAD7AAAAFgBTAHQAeQBsAGUARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAgAEsAaQBzAEgAaQBzAHQAbwBnAHIAYQBtAEQAbwBjAGsAAAAAAP////8AAAAAAAAAAPsAAAASAFMAYwByAGkAcAB0AGkAbgBnAAAAAAD/////AAAAAAAAAAD7AAAAMABEAGUAZgBhAHUAbAB0AFQAbwBvAGwAQQByAHIAYQBuAGcAZQBXAGkAZABnAGUAdAAAAAK8AAAAUgAAAAAAAAAA+wAAACIARABlAGYAYQB1AGwAdABUAG8AbwBsAFcAaQBkAGcAZQB0AAAAAxEAAABbAAAAAAAAAAD7AAAAJABLAGkAcwBIAGkAcwB0AG8AZwByAGEAbQBEAG8AYwBrAGUAcgAAAAJCAAAAewAAAAAAAAAA+wAAABgARABpAGcAaQB0AGEAbABNAGkAeABlAHIAAAAAAP////8AAACTAP////sAAAAOAEgAaQBzAHQAbwByAHkAAAADkAAAALQAAACuAP////sAAABOAEsAcgBpAHQAYQBGAGkAbABsAC8ASwBpAHMAVABvAG8AbABHAHIAYQBkAGkAZQBuAHQAIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AAAABCgAAAAcAAAAAAAAAAD7AAAARgBLAHIAaQB0AGEARgBpAGwAbAAvAEsAaQBzAFQAbwBvAGwARgBpAGwAbAAgAG8AcAB0AGkAbwBuACAAdwBpAGQAZwBlAHQAAAADUAAAABwAAAAAAAAAAPsAAAA2AEsAcgBpAHQAYQBTAGgAYQBwAGUALwBLAGkAcwBUAG8AbwBsAFIAZQBjAHQAYQBuAGcAbABlAAAAAwUAAABnAAAAAAAAAAD7AAAAIgBDAG8AbQBwAG8AcwBpAHQAaQBvAG4ARABvAGMAawBlAHIAAAAAAP////8AAACMAP////sAAAAqAEEAcgB0AGkAcwB0AGkAYwBDAG8AbABvAHIAUwBlAGwAZQBjAHQAbwByAAAAAAD/////AAAAdwD////7AAAAGgBQAGEAdAB0AGUAcgBuAEQAbwBjAGsAZQByAAAAAtkAAAFJAAAAswD////7AAAAGgBUAGEAcwBrAHMAZQB0AEQAbwBjAGsAZQByAAAAAAD/////AAAAjAD////7AAAAKABTAG4AYQBwAEcAdQBpAGQAZQAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAA4AFQAZQB4AHQARABvAGMAdQBtAGUAbgB0AEkAbgBzAHAAZQBjAHQAaQBvAG4ARABvAGMAawBlAHICAAAEmgAAAhUAAAEqAAAArvsAAAASAEwAdQB0AEQAbwBjAGsAZQByAAAAAAD/////AAABXQD////7AAAAGgBQAGEAbABlAHQAdABlAEQAbwBjAGsAZQByAAAAAAD/////AAAAQgD////7AAAAFABHAHIAaQBkAEQAbwBjAGsAZQByAAAAAAD/////AAABLQD////7AAAAHgBIAGkAcwB0AG8AZwByAGEAbQBEAG8AYwBrAGUAcgAAAAAA/////wAAAEcA////+wAAACoAQQBuAGkAbQBhAHQAaQBvAG4AQwB1AHIAdgBlAHMARABvAGMAawBlAHIAAAAAAP////8AAACMAP////sAAAAyAFMAdgBnAFMAeQBtAGIAbwBsAEMAbwBsAGwAZQBjAHQAaQBvAG4ARABvAGMAawBlAHIAAAAAAP////8AAACMAP////sAAAAWAFQAbwB1AGMAaABEAG8AYwBrAGUAcgAAAAJMAAABMQAAABMA////+wAAABoAQQByAHIAYQBuAGcAZQBEAG8AYwBrAGUAcgAAAAAA/////wAAAHoA////+wAAADoAYwBvAG0AaQBjAHMAXwBwAHIAbwBqAGUAYwB0AF8AbQBhAG4AYQBnAGUAcgBfAGQAbwBjAGsAZQByAAAAAAD/////AAAAuQD////7AAAAKgBxAHUAaQBjAGsAXwBzAGUAdAB0AGkAbgBnAHMAXwBkAG8AYwBrAGUAcgAAAAAA/////wAAAIwA////+wAAABYAUABhAGcAZQByAEQAbwBjAGsAZQByAAAAAAD/////AAAALQD////7AAAAJgBsAGEAcwB0AGQAbwBjAHUAbQBlAG4AdABzAGQAbwBjAGsAZQByAAAAAAD/////AAAAiQD///8AAAACAAAKAAAAALz8AQAAAAH7AAAAGgBUAG8AbwBsAEIAYQByAEQAbwBjAGsAZQByAAAAAAD/////AAAAAAAAAAAAAAADAAAAAAAAAAD8AQAAAAT7AAAAHABGAGwAaQBwAGIAbwBvAGsARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAeAEEAbgBpAG0AYQB0AGkAbwBuAEQAbwBjAGsAZQByAAAAAAD/////AAABBQD////7AAAAIABPAG4AaQBvAG4AUwBrAGkAbgBzAEQAbwBjAGsAZQByAAAAAAD/////AAABNgD////7AAAAHABUAGkAbQBlAGwAaQBuAGUARABvAGMAawBlAHIAAAAAAP////8AAABVAP///wAABU0AAAPNAAAABAAAAAQAAAAIAAAACPwAAAABAAAAAgAAAAIAAAAWAG0AYQBpAG4AVABvAG8AbABCAGEAcgEAAAAA/////wAAAAAAAAAAAAAAHgBCAHIAdQBzAGgAZQBzAEEAbgBkAFMAdAB1AGYAZgEAAAC5/////wAAAAAAAAAA [advancedColorSelector] gamma=2.2000000000000002 hidePopupOnClickCheck=false hsxSettingType=0 lumaB=0.0722 lumaG=0.71519999999999995 lumaR=0.21260000000000001 onDockerResize=0 shadeMyPaintType=HSV zoomSelectorOptions=0 [calligra] ColorSpaceExtensionsPlugins=\\0 ColorSpaceExtensionsPluginsDisabled= ColorSpacePlugins=\\0 ColorSpacePluginsDisabled= DockerPlugins=\\0 DockerPluginsDisabled=textdocumentinspection FlakePlugins=, ShapePlugins=, ToolsBlacklist=CreatePathTool,KoPencilTool,ConnectionTool,KarbonFilterEffectsTool,KritaShape/KisToolText,ArtisticTextTool,TextTool ToolPlugins=,, ToolPluginsDisabled= -[KoShapeCollection] -QuickShapes=ArtisticText,TextShapeID,EllipseShape,RectangleShape - [colorhotkeys] steps_blueyellow=10 steps_hue=36 steps_lightness=10 steps_redgreen=10 steps_saturation=10 [crashprevention] CreatingCanvas=false [hsxColorSlider] hsiH=false hsiI=false hsiS=false hslH=true hslL=true hslS=true hsvH=false hsvS=false hsvV=false hsyH=false hsyS=false hsyY=false [krita] State=AAAA/wAAAAD9AAAABAAAAAAAAABEAAAE6PwCAAAAA/sAAAAOAFQAbwBvAGwAQgBvAHgBAAAARAAABOgAAAAdAQAAA/sAAAAkAEYAbABvAHcAUwBoAGEAcABlAEIAbwB4AEQAbwBjAGsAZQByAAAAA2oAAADHAAAAAAAAAAD7AAAAKABGAGwAbwB3AFMAdABlAG4AYwBpAGwAQgBvAHgARABvAGMAawBlAHIAAAADfQAAAMcAAAAAAAAAAAAAAAEAAAEZAAAE6PwCAAAAO/sAAAAaAEsAaQBzAEIAaQByAGQAZQB5AGUAQgBvAHgAAAAAAP////8AAAAAAAAAAPsAAAAgAEsAaQBzAFAAYQBsAGUAdAB0AGUARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAaAEsAbwBDAG8AbABvAHIARABvAGMAawBlAHIAAAAAAP////8AAAAAAAAAAPsAAAAwAEsAaQBzAFQAcgBpAGEAbgBnAGwAZQBDAG8AbABvAHIAUwBlAGwAZQBjAHQAbwByAAAAAAD/////AAAAAAAAAAD7AAAAIgBTAGgAYQBkAG8AdwAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAAgAFMAaABhAHAAZQAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAAaAFMAaABhAHAAZQBTAGUAbABlAGMAdABvAHIAAAAASAAAAEQAAAAAAAAAAPsAAAAkAFMAaQBtAHAAbABlACAAVABlAHgAdAAgAEUAZABpAHQAbwByAAAAAAD/////AAAAAAAAAAD8AAAARAAAAKUAAAAAAP////r/////AQAAAAL7AAAAFgBDAG8AbABvAHIAUwBsAGkAZABlAHIAAAAAAP////8AAACuAQAAA/sAAAAaAFAAYQBsAGUAdAB0AGUARABvAGMAawBlAHIAAAAAAP////8AAADtAQAAA/wAAABEAAABMgAAAIgBAAAb+gAAAAIBAAAABvsAAAAcAE8AdgBlAHIAdgBpAGUAdwBEAG8AYwBrAGUAcgEAAAAA/////wAAAKMBAAAD+wAAACAAcwBoAGEAcgBlAGQAdABvAG8AbABkAG8AYwBrAGUAcgEAAAAA/////wAAAJgBAAAD+wAAAB4AQwBvAGwAbwByAFMAZQBsAGUAYwB0AG8AcgBOAGcBAAAAAP////8AAAD7AQAAA/sAAAAqAFMAcABlAGMAaQBmAGkAYwBDAG8AbABvAHIAUwBlAGwAZQBjAHQAbwByAAAAAAD/////AAAA5gEAAAP7AAAAFgBJAG0AYQBnAGUARABvAGMAawBlAHIAAAAAAP////8AAADbAQAAA/sAAAAqAFMAaABhAHAAZQBDAG8AbABsAGUAYwB0AGkAbwBuAEQAbwBjAGsAZQByAAAABkgAAAEoAAAAAAAAAAD7AAAARgBLAHIAaQB0AGEAUwBoAGEAcABlAC8ASwBpAHMAVABvAG8AbABEAHkAbgBhAG8AcAB0AGkAbwBuACAAdwBpAGQAZwBlAHQBAAAAUgAAABIAAAAAAAAAAPsAAAAsAEsAcgBpAHQAYQBTAGgAYQBwAGUALwBLAGkAcwBUAG8AbwBsAEwAaQBuAGUBAAAAPAAAAGkAAAAAAAAAAPsAAAAyAEsAcgBpAHQAYQBTAGgAYQBwAGUALwBLAGkAcwBUAG8AbwBsAEUAbABsAGkAcABzAGUBAAAAkQAAABIAAAAAAAAAAPsAAAAcAEsAaQBzAFQAbwBvAGwAUABvAGwAeQBnAG8AbgEAAACmAAAAEgAAAAAAAAAA+wAAAB4ASwBpAHMAVABvAG8AbABQAG8AbAB5AGwAaQBuAGUBAAAAuwAAABIAAAAAAAAAAPsAAAAWAEsAaQBzAFQAbwBvAGwAUwB0AGEAcgEAAADQAAAAEwAAAAAAAAAA+wAAACoAUwBuAGEAcABHAHUAaQBkAGUAQwBvAG4AZgBpAGcAVwBpAGQAZwBlAHQAAAAA7wAAAHEAAAAAAAAAAPsAAAAyAEsAaQBzAFQAbwBvAGwAQwByAG8AcAAgAG8AcAB0AGkAbwBuACAAdwBpAGQAZwBlAHQBAAAA+wAAABIAAAAAAAAAAPsAAABQAEsAcgBpAHQAYQBUAHIAYQBuAHMAZgBvAHIAbQAvAEsAaQBzAFQAbwBvAGwATQBvAHYAZQAgAE8AcAB0AGkAbwBuACAAVwBpAGQAZwBlAHQBAAABEAAAABIAAAAAAAAAAPsAAAA8AEsAaQBzAFQAbwBvAGwAVAByAGEAbgBzAGYAbwByAG0AIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAADwAAAAvAAAAAAAAAAD7AAAATgBLAHIAaQB0AGEAUwBoAGEAcABlAC8ASwBpAHMAVABvAG8AbABNAGUAYQBzAHUAcgBlACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAA8AAAAQgAAAAAAAAAA+wAAAFwASwByAGkAdABhAFMAZQBsAGUAYwB0AGUAZAAvAEsAaQBzAFQAbwBvAGwAQwBvAGwAbwByAFAAaQBjAGsAZQByACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAA8AAAA/wAAAAAAAAAA+wAAAEYASwBpAHMAUgB1AGwAZQByAEEAcwBzAGkAcwB0AGEAbgB0AFQAbwBvAGwAIABPAHAAdABpAG8AbgAgAFcAaQBkAGcAZQB0AQAAADwAAAASAAAAAAAAAAD7AAAASABLAGkAcwBUAG8AbwBsAFAAZQByAHMAcABlAGMAdABpAHYAZQBHAHIAaQBkACAATwBwAHQAaQBvAG4AIABXAGkAZABnAGUAdAEAAAGjAAAAEgAAAAAAAAAA+wAAADIASwBpAHMAVABvAG8AbABHAHIAaQBkACAATwBwAHQAaQBvAG4AIABXAGkAZABnAGUAdAEAAAG4AAAAEwAAAAAAAAAA+wAAAEwASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABSAGUAYwB0AGEAbgBnAHUAbABhAHIAIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAAc4AAAASAAAAAAAAAAD7AAAASgBLAGkAcwBUAG8AbwBsAFMAZQBsAGUAYwB0AEUAbABsAGkAcAB0AGkAYwBhAGwAIABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAAeMAAAASAAAAAAAAAAD7AAAASABLAGkAcwBUAG8AbwBsAFMAZQBsAGUAYwB0AFAAbwBsAHkAZwBvAG4AYQBsACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAH4AAAAEgAAAAAAAAAA+wAAAEQASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABPAHUAdABsAGkAbgBlACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAINAAAAEgAAAAAAAAAA+wAAAEoASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABDAG8AbgB0AGkAZwB1AG8AdQBzACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAIiAAAAEgAAAAAAAAAA+wAAAEQASwBpAHMAVABvAG8AbABTAGUAbABlAGMAdABTAGkAbQBpAGwAYQByACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAEAAAI3AAAAEgAAAAAAAAAA/AAAAbYAAABaAAAAAAD////6AAAAAAEAAAAC+wAAAC4ASwBvAFMAaABhAHAAZQBDAG8AbABsAGUAYwB0AGkAbwBuAEQAbwBjAGsAZQByAQAAAAD/////AAAAAAAAAAD7AAAAJABTAG0AYQBsAGwAQwBvAGwAbwByAFMAZQBsAGUAYwB0AG8AcgAAAANuAAABBAAAANkBAAAD/AAAAXcAAAGjAAAA3gEAABv6AAAAAAEAAAAF+wAAABYASwBpAHMATABhAHkAZQByAEIAbwB4AQAAAAD/////AAABBgEAAAP7AAAAIgBDAG8AbQBwAG8AcwBpAHQAaQBvAG4ARABvAGMAawBlAHIAAAAAAP////8AAAC0AQAAA/sAAAAOAEgAaQBzAHQAbwByAHkAAAAAAP////8AAACxAQAAA/sAAAAaAEMAaABhAG4AbgBlAGwARABvAGMAawBlAHIBAAAAAP////8AAACjAQAAA/sAAAAuAEsAaQBzAFAAYQBpAG4AdABlAHIAbAB5AE0AaQB4AGUAcgBEAG8AYwBrAGUAcgAAAAAA/////wAAAAAAAAAA+wAAABgAUAByAGUAcwBlAHQARABvAGMAawBlAHIBAAADGwAAAhEAAACCAQAAA/sAAABIAEsAcgBpAHQAYQBTAGgAYQBwAGUALwBLAGkAcwBUAG8AbwBsAEIAcgB1AHMAaABvAHAAdABpAG8AbgAgAHcAaQBkAGcAZQB0AQAAA9wAAABoAAAAAAAAAAD7AAAAIgBTAHQAcgBvAGsAZQAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAAWAFMAdAB5AGwAZQBEAG8AYwBrAGUAcgAAAAAA/////wAAAAAAAAAA+wAAACAASwBpAHMASABpAHMAdABvAGcAcgBhAG0ARABvAGMAawAAAAAA/////wAAAAAAAAAA+wAAABIAUwBjAHIAaQBwAHQAaQBuAGcAAAAAAP////8AAAAAAAAAAPsAAAAwAEQAZQBmAGEAdQBsAHQAVABvAG8AbABBAHIAcgBhAG4AZwBlAFcAaQBkAGcAZQB0AAAAArwAAABSAAAAAAAAAAD7AAAAIgBEAGUAZgBhAHUAbAB0AFQAbwBvAGwAVwBpAGQAZwBlAHQAAAADEQAAAFsAAAAAAAAAAPsAAAAkAEsAaQBzAEgAaQBzAHQAbwBnAHIAYQBtAEQAbwBjAGsAZQByAAAAAkIAAAB7AAAAAAAAAAD7AAAAGABEAGkAZwBpAHQAYQBsAE0AaQB4AGUAcgAAAAAA/////wAAAKEBAAAD+wAAAE4ASwByAGkAdABhAEYAaQBsAGwALwBLAGkAcwBUAG8AbwBsAEcAcgBhAGQAaQBlAG4AdAAgAG8AcAB0AGkAbwBuACAAdwBpAGQAZwBlAHQAAAAEKAAAABwAAAAAAAAAAPsAAABGAEsAcgBpAHQAYQBGAGkAbABsAC8ASwBpAHMAVABvAG8AbABGAGkAbABsACAAbwBwAHQAaQBvAG4AIAB3AGkAZABnAGUAdAAAAANQAAAAHAAAAAAAAAAA+wAAADYASwByAGkAdABhAFMAaABhAHAAZQAvAEsAaQBzAFQAbwBvAGwAUgBlAGMAdABhAG4AZwBsAGUAAAADBQAAAGcAAAAAAAAAAPsAAAAqAEEAcgB0AGkAcwB0AGkAYwBDAG8AbABvAHIAUwBlAGwAZQBjAHQAbwByAAAAAAD/////AAAAgAEAAAP7AAAAGgBQAGEAdAB0AGUAcgBuAEQAbwBjAGsAZQByAAAAAtkAAAFJAAAAvAEAAAP7AAAAGgBUAGEAcwBrAHMAZQB0AEQAbwBjAGsAZQByAAAAAAD/////AAAAmAEAAAP7AAAAKABTAG4AYQBwAEcAdQBpAGQAZQAgAFAAcgBvAHAAZQByAHQAaQBlAHMAAAAAAP////8AAAAAAAAAAPsAAAA4AFQAZQB4AHQARABvAGMAdQBtAGUAbgB0AEkAbgBzAHAAZQBjAHQAaQBvAG4ARABvAGMAawBlAHICAAAEmgAAAhUAAAEqAAAArvsAAAASAEwAdQB0AEQAbwBjAGsAZQByAAAAA3wAAAEuAAABsQEAAAP7AAAAFABHAHIAaQBkAEQAbwBjAGsAZQByAAAAAAD/////AAABNgEAAAP7AAAAHgBIAGkAcwB0AG8AZwByAGEAbQBEAG8AYwBrAGUAcgAAAAAA/////wAAAFABAAAD+wAAABoAUAByAGUAcwBlAHQASABpAHMAdABvAHIAeQAAAAAA/////wAAAHABAAAD+wAAADIAUwB2AGcAUwB5AG0AYgBvAGwAQwBvAGwAbABlAGMAdABpAG8AbgBEAG8AYwBrAGUAcgAAAAAA/////wAAAAAAAAAA+wAAABYAVABvAHUAYwBoAEQAbwBjAGsAZQByAAAAAAD/////AAAAHAEAAAP7AAAAGgBBAHIAcgBhAG4AZwBlAEQAbwBjAGsAZQByAAAAAAD/////AAAAkAEAAAP7AAAAKgBBAG4AaQBtAGEAdABpAG8AbgBDAHUAcgB2AGUAcwBEAG8AYwBrAGUAcgAAAAAA/////wAAAJgBAAADAAAAAgAAB4AAAAC8/AEAAAAB+wAAABoAVABvAG8AbABCAGEAcgBEAG8AYwBrAGUAcgAAAAAA/////wAAAAAAAAAAAAAAAwAAAAAAAAAA/AEAAAAE+wAAABwARgBsAGkAcABiAG8AbwBrAEQAbwBjAGsAZQByAAAAAAD/////AAAAAAAAAAD7AAAAIABPAG4AaQBvAG4AUwBrAGkAbgBzAEQAbwBjAGsAZQByAAAAAAD/////AAABSAEAAAP7AAAAHgBBAG4AaQBtAGEAdABpAG8AbgBEAG8AYwBrAGUAcgAAAAAA/////wAAASUBAAAD+wAAABwAVABpAG0AZQBsAGkAbgBlAEQAbwBjAGsAZQByAAAAAAD/////AAAAlgEAAAMAAAihAAAE6AAAAAQAAAAEAAAACAAAAAj8AAAAAQAAAAIAAAACAAAAFgBtAGEAaQBuAFQAbwBvAGwAQgBhAHIBAAAAAP////8AAAAAAAAAAAAAAB4AQgByAHUAcwBoAGUAcwBBAG4AZABTAHQAdQBmAGYBAAAA1P////8AAAAAAAAAAA== ToolBarsMovable=Enabled [krita][DockWidget AnimationCurvesDocker] Collapsed=false DockArea=2 Locked=false height=421 width=448 xPosition=0 yPosition=0 [krita][DockWidget AnimationDocker] Collapsed=false DockArea=8 Locked=false height=160 width=280 xPosition=0 yPosition=0 [krita][DockWidget ArtisticColorSelector] Collapsed=false DockArea=2 Locked=false height=294 width=337 xPosition=0 yPosition=0 [krita][DockWidget ChannelDocker] Collapsed=false DockArea=2 Locked=false height=30 width=100 xPosition=0 yPosition=0 [krita][DockWidget ColorSelectorNg] Collapsed=false DockArea=2 Locked=false height=176 width=281 xPosition=0 yPosition=20 [krita][DockWidget ColorSlider] Collapsed=false DockArea=2 Locked=false height=460 width=640 xPosition=0 yPosition=20 [krita][DockWidget CompositionDocker] Collapsed=false DockArea=2 Locked=false height=300 width=400 xPosition=0 yPosition=0 [krita][DockWidget DigitalMixer] Collapsed=false DockArea=2 Locked=false height=30 width=100 xPosition=0 yPosition=0 [krita][DockWidget GridDocker] Collapsed=false DockArea=2 Locked=false height=342 width=441 xPosition=0 yPosition=0 [krita][DockWidget HistogramDocker] Collapsed=false DockArea=2 Locked=false height=91 width=281 xPosition=0 yPosition=20 [krita][DockWidget History] Collapsed=false DockArea=2 Locked=false height=460 width=640 xPosition=0 yPosition=20 [krita][DockWidget ImageDocker] Collapsed=false DockArea=2 Locked=false height=300 width=399 xPosition=0 yPosition=0 [krita][DockWidget KisLayerBox] DockArea=2 Locked=false height=358 width=281 xPosition=0 yPosition=20 [krita][DockWidget LutDocker] Collapsed=false DockArea=2 Locked=false height=286 width=357 xPosition=0 yPosition=0 [krita][DockWidget OnionSkinsDocker] Collapsed=false DockArea=8 Locked=false height=210 width=356 xPosition=0 yPosition=0 [krita][DockWidget OverviewDocker] Collapsed=false DockArea=2 Locked=false height=30 width=100 xPosition=0 yPosition=0 [krita][DockWidget PaletteDocker] Collapsed=false DockArea=2 Locked=false height=219 width=256 xPosition=0 yPosition=0 [krita][DockWidget PatternDocker] Collapsed=false DockArea=2 Locked=false height=30 width=100 xPosition=0 yPosition=0 [krita][DockWidget PresetDocker] Collapsed=false DockArea=2 Locked=false height=460 width=640 xPosition=0 yPosition=20 [krita][DockWidget PresetHistory] Collapsed=false DockArea=2 Locked=false height=30 width=100 xPosition=0 yPosition=0 [krita][DockWidget Shape Properties] DockArea=2 Locked=false height=480 width=640 xPosition=0 yPosition=0 [krita][DockWidget ShapeCollectionDocker] Collapsed=false DockArea=2 Locked=false height=0 width=0 xPosition=0 yPosition=20 [krita][DockWidget SmallColorSelector] DockArea=2 Locked=false height=460 width=640 xPosition=0 yPosition=20 [krita][DockWidget SpecificColorSelector] DockArea=2 Locked=false height=460 width=640 xPosition=0 yPosition=20 [krita][DockWidget TasksetDocker] Collapsed=false DockArea=2 Locked=false height=300 width=400 xPosition=0 yPosition=0 [krita][DockWidget TimelineDocker] Collapsed=false DockArea=8 Locked=false height=30 width=100 xPosition=0 yPosition=0 [krita][DockWidget ToolBox] DockArea=1 Locked=false height=610 width=63 xPosition=0 yPosition=20 [krita][DockWidget sharedtooldocker] Collapsed=false DockArea=2 Locked=false height=460 width=640 xPosition=0 yPosition=20 [krita][Toolbar mainToolBar] ToolButtonStyle=IconOnly [TemplateChooserDialog] ShowCustomDocumentWidgetByDefault=true LastReturnType=Custom Document [theme] Theme=Krita dark [python] enable_colorspace=true enable_comics_project_management_tools=true enable_documenttools=true enable_exportlayers=true enable_filtermanager=true enable_lastdocumentsdocker=true enable_quick_settings_docker=true enable_scripter=true enable_tenbrushes=true enable_tenscripts=true enable_plugin_importer=true diff --git a/krita/data/templates/dslr/Canon_550D_5184x3456.desktop b/krita/data/templates/dslr/Canon_550D_5184x3456.desktop index c9a0ef193f..18c6a4d009 100755 --- a/krita/data/templates/dslr/Canon_550D_5184x3456.desktop +++ b/krita/data/templates/dslr/Canon_550D_5184x3456.desktop @@ -1,41 +1,41 @@ [Desktop Entry] Icon=template_dslr Name=Canon_550D_5184x3456 Name[bs]=Canon_550D_5184x3456 Name[ca]=Canon_550D_5184x3456 Name[ca@valencia]=Canon_550D_5184x3456 Name[cs]=Canon_550D_5184x3456 Name[da]=Canon_550D_5184x3456 Name[de]=Canon 550D 5184x3456 Name[el]=Canon_550D_5184x3456 Name[en_GB]=Canon_550D_5184x3456 Name[es]=Canon_550D_5184x3456 Name[et]=Canon_550D_5184x3456 Name[eu]=Canon_550D_5184x3456 Name[fi]=Canon 550D 5184 × 3456 Name[fr]=Canon_550D_5184x3456 Name[gl]=Canon 550D (5184×3456) Name[hu]=Canon_550D_5184x3456 Name[is]=Canon_550D_5184x3456 Name[it]=Canon_550D_5184x3456 Name[ja]=キヤノンEOS Kiss X4(5184x3456) Name[kk]=Canon_550D_5184x3456 Name[ko]=캐논_550D_5184x3456 Name[nb]=Canon_550D_5184x3456 Name[nl]=Canon_550D_5184x3456 -Name[nn]=Canon EOS 550D (5184 × 3456) +Name[nn]=Canon EOS 550D (5184 × 3456) Name[pl]=Canon_550D_5184x3456 Name[pt]=Canon_550D_5184x3456 Name[pt_BR]=Canon 550D 5184x3456 Name[ru]=Canon_550D_5184x3456 Name[sk]=Canon_550D_5184x3456 Name[sl]=Canon_550D_5184x3456 Name[sv]=Canon_550D_5184x3456 Name[tr]=Canon_550D_5184x3456 Name[uk]=Canon 550D 5184⨯3456 Name[x-test]=xxCanon_550D_5184x3456xx Name[zh_CN]=佳能 550D 相机模板 5184x3456 像素 Name[zh_TW]=Canon_550D_5184x3456 Type=Link URL[$e]=.source/Canon_550D_5184x3456.kra X-KDE-Hidden=false diff --git a/krita/data/templates/dslr/Canon_5Dmk3_5760x3840.desktop b/krita/data/templates/dslr/Canon_5Dmk3_5760x3840.desktop index 1e37dc959e..a26cd350ad 100755 --- a/krita/data/templates/dslr/Canon_5Dmk3_5760x3840.desktop +++ b/krita/data/templates/dslr/Canon_5Dmk3_5760x3840.desktop @@ -1,41 +1,41 @@ [Desktop Entry] Icon=template_dslr Name=Canon_5Dmk3_5760x3840 Name[bs]=Canon_5Dmk3_5760x3840 Name[ca]=Canon_5Dmk3_5760x3840 Name[ca@valencia]=Canon_5Dmk3_5760x3840 Name[cs]=Canon_5Dmk3_5760x3840 Name[da]=Canon_5Dmk3_5760x3840 Name[de]=Canon 5Dmk3 5760x3840 Name[el]=Canon_5Dmk3_5760x3840 Name[en_GB]=Canon_5Dmk3_5760x3840 Name[es]=Canon_5Dmk3_5760x3840 Name[et]=Canon_5Dmk3_5760x3840 Name[eu]=Canon_5Dmk3_5760x3840 Name[fi]=Canon 5Dmk3 5760 × 3840 Name[fr]=Canon_5Dmk3_5760x3840 Name[gl]=Canon 5Dmk3 (5760×3840) Name[hu]=Canon_5Dmk3_5760x3840 Name[is]=Canon_5Dmk3_5760x3840 Name[it]=Canon_5Dmk3_5760x3840 Name[ja]=キヤノンEOS 5D Mark III(5760x3840) Name[kk]=Canon_5Dmk3_5760x3840 Name[ko]=캐논_5Dmk3_5760x3840 Name[nb]=Canon_5Dmk3_5760x3840 Name[nl]=Canon_5Dmk3_5760x3840 -Name[nn]=Canon EOS 5D Mark III (5760 × 3840) +Name[nn]=Canon EOS 5D Mark III (5760 × 3840) Name[pl]=Canon_5Dmk3_5760x3840 Name[pt]=Canon_5Dmk3_5760x3840 Name[pt_BR]=Canon 5D Mark III 5760x3840 Name[ru]=Canon_5Dmk3_5760x3840 Name[sk]=Canon_5Dmk3_5760x3840 Name[sl]=Canon_5Dmk3_5760x3840 Name[sv]=Canon_5Dmk3_5760x3840 Name[tr]=Canon_5Dmk3_5760x3840 Name[uk]=Canon 5Dmk3 5760⨯3840 Name[x-test]=xxCanon_5Dmk3_5760x3840xx Name[zh_CN]=佳能 5Dmk3 相机模板 5760x3840 像素 Name[zh_TW]=Canon_5Dmk3_5760x3840 Type=Link URL[$e]=.source/Canon_5Dmk3_5760x3840.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop b/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop index 947fe568c7..366492f10b 100644 --- a/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop +++ b/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop @@ -1,38 +1,38 @@ [Desktop Entry] Icon=template_texture Name=Texture 1024x1024 8bit srgb Name[bs]=Tekstura 1024x1024 8bit srgb Name[ca]=Textura 1024x1024 de 8 bits amb SRGB Name[ca@valencia]=Textura 1024x1024 de 8 bits amb SRGB Name[cs]=Textura 1024x1024 8bit srgb Name[da]=Tekstur 1024x1024 8bit srgb Name[de]=Textur 1024x1024 8bit srgb Name[el]=Υφή 1024x1024 8bit srgb Name[en_GB]=Texture 1024x1024 8bit srgb Name[es]=Textura 1024x1024 8bits srgb Name[et]=Tekstuur 1024x1024 8bit srgb Name[eu]=Ehundura 1024x1024 8bit sRGB Name[fi]=Pintakuvio 1024 × 1024 8 bit SRGB Name[fr]=Texture 1024x1024 8bit srgb Name[gl]=Textura de 1024×1024 e 8 bits SRGB Name[is]=Efnisáferð 1024x1024 8bita srgb Name[it]=Trama 1024x1024 8bit srgb Name[ja]=テクスチャ 1024x1024 8 ビット sRGB Name[ko]=텍스처 1024 x 1024 8비트 srgb Name[nb]=Tekstur 1024x1024 8bit srgb Name[nl]=Textuur 1024x1024 8bit srgb -Name[nn]=Tekstur 1024 × 1024 8-bits SRGB +Name[nn]=Tekstur 1024 × 1024 8-bits SRGB Name[pl]=Tekstura 1024x1024 8bit srgb Name[pt]=Textura 1024x1024 8-bits sRGB Name[pt_BR]=Textura 1024x1024 8-bits sRGB Name[ru]=Текстура 1024x1024 8 бит srgb Name[sk]=Textúra 1024x1024 8bit srgb Name[sv]=Struktur 1024 x 1024 8-bitar SRGB Name[tr]=Doku 1024x1024 8bit srgb Name[uk]=Текстура 1024⨯1024, 8-бітова, srgb Name[x-test]=xxTexture 1024x1024 8bit srgbxx Name[zh_CN]=1024x1024 纹理模板 8位 sRGB 色彩空间 Name[zh_TW]=紋理 1024x1024 8位元 srgb Type=Link URL[$e]=.source/Texture1024x10248bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop b/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop index 3e8105dc44..c9e32f4aa0 100644 --- a/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop +++ b/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop @@ -1,38 +1,38 @@ [Desktop Entry] Icon=template_texture Name=Texture 2048x2048 8bit srgb Name[bs]=Tekstura 2048x2048 8bit srgb Name[ca]=Textura 2048x2048 de 8 bits amb SRGB Name[ca@valencia]=Textura 2048x2048 de 8 bits amb SRGB Name[cs]=Textura 2048x2048 8bit srgb Name[da]=Tekstur 2048x2048 8bit srgb Name[de]=Textur 2048x2048 8bit srgb Name[el]=Υφή 2048x2048 8bit srgb Name[en_GB]=Texture 2048x2048 8bit srgb Name[es]=Textura 2048x2048 8bits srgb Name[et]=Tekstuur 2048x2048 8bit srgb Name[eu]=Ehundura 2048x2048 8bit sGBU Name[fi]=Pintakuvio 2048 × 2048 8 bit SRGB Name[fr]=Texture 2048x2048 8bit srgb Name[gl]=Textura de 2048×2048 e 8 bits SRGB Name[is]=Efnisáferð 2048x2048 8bita srgb Name[it]=Trama 2048x2048 8bit srgb Name[ja]=テクスチャ 2048x2048 8 ビット sRGB Name[ko]=텍스처 2048x2048 8비트 srgb Name[nb]=Tekstur 2048x2048 8bit srgb Name[nl]=Textuur 2048x2048 8bit srgb -Name[nn]=Tekstur 2048 × 2048 8-bits SRGB +Name[nn]=Tekstur 2048 × 2048 8-bits SRGB Name[pl]=Tekstura 2048x2048 8bit srgb Name[pt]=Textura 2048x2048 8-bits sRGB Name[pt_BR]=Textura 2048x2048 8bits sRGB Name[ru]=Текстура 2048x2048 8 бит srgb Name[sk]=Textúra 2048x2048 8bit srgb Name[sv]=Struktur 2048 x 2048 8-bitar SRGB Name[tr]=Doku 2048x2048 8bit srgb Name[uk]=Текстура 2048⨯2048, 8-бітова, srgb Name[x-test]=xxTexture 2048x2048 8bit srgbxx Name[zh_CN]=2048x2048 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 2048x2048 8位元 srgb Type=Link URL[$e]=.source/Texture2048x20488bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture256x2568bitsrgb.desktop b/krita/data/templates/texture/Texture256x2568bitsrgb.desktop index 8ff7b0cad9..67c48b6fb7 100644 --- a/krita/data/templates/texture/Texture256x2568bitsrgb.desktop +++ b/krita/data/templates/texture/Texture256x2568bitsrgb.desktop @@ -1,38 +1,38 @@ [Desktop Entry] Icon=template_texture Name=Texture 256x256 8bit srgb Name[bs]=Tekstura 256x256 8bit srgb Name[ca]=Textura 256x256 de 8 bits amb SRGB Name[ca@valencia]=Textura 256x256 de 8 bits amb SRGB Name[cs]=Textura 256x256 8bit srgb Name[da]=Tekstur 256x256 8bit srgb Name[de]=Textur 256x256 8bit srgb Name[el]=Υφή 256x256 8bit srgb Name[en_GB]=Texture 256x256 8bit srgb Name[es]=Textura 256x256 8bits srgb Name[et]=Tekstuur 256x256 8bit srgb Name[eu]=Ehundura 256x256 8bit sGBU Name[fi]=Pintakuvio 256 × 256 8 bit SRGB Name[fr]=Texture 256x256 8bit srgb Name[gl]=Textura de 256×256 e 8 bits SRGB Name[is]=Efnisáferð 256x256 8bita srgb Name[it]=Trama 256x256 8bit srgb Name[ja]=テクスチャ 256x256 8 ビット sRGB Name[ko]=텍스처 256x256 8비트 srgb Name[nb]=Tekstur 256x256 8bit srgb Name[nl]=Textuur 256x256 8bit srgb -Name[nn]=Tekstur 256 × 256 8-bits SRGB +Name[nn]=Tekstur 256 × 256 8-bits SRGB Name[pl]=Tekstura 256x256 8bit srgb Name[pt]=Textura 256x256 8-bits sRGB Name[pt_BR]=Textura 256x256 8bits sRGB Name[ru]=Текстура 256x256 8 бит srgb Name[sk]=Textúra 256x256 8bit srgb Name[sv]=Struktur 256 x 256 8-bitar SRGB Name[tr]=Doku 256x256 8bit srgb Name[uk]=Текстура 256⨯256, 8-бітова, srgb Name[x-test]=xxTexture 256x256 8bit srgbxx Name[zh_CN]=256x256 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 256x256 8位元 srgb Type=Link URL[$e]=.source/Texture256x2568bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop b/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop index 8674cbe1f0..750d3b4729 100644 --- a/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop +++ b/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop @@ -1,38 +1,38 @@ [Desktop Entry] Icon=template_texture Name=Texture 4096x4096 8bit srgb Name[bs]=Tekstura 4096x4096 8bit srgb Name[ca]=Textura 4096x4096 de 8 bits amb SRGB Name[ca@valencia]=Textura 4096x4096 de 8 bits amb SRGB Name[cs]=Textura 4096x4096 8bit srgb Name[da]=Tekstur 4096x4096 8bit srgb Name[de]=Textur 4096x4096 8bit srgb Name[el]=Υφή 4096x4096 8bit srgb Name[en_GB]=Texture 4096x4096 8bit srgb Name[es]=Textura 4096x4096 8bits srgb Name[et]=Tekstuur 4096x4096 8bit srgb Name[eu]=Ehundura 4096x4096 8bit sGBU Name[fi]=Pintakuvio 4096 × 4096 8 bit SRGB Name[fr]=Texture 4096x4096 8bit srgb Name[gl]=Textura de 4096×4096 e 8 bits SRGB Name[is]=Efnisáferð 4096x4096 8bita srgb Name[it]=Trama 4096x4096 8bit srgb Name[ja]=テクスチャ 4096x4096 8 ビット sRGB Name[ko]=텍스처 4096x4096 8비트 srgb Name[nb]=Tekstur 4096x4096 8bit srgb Name[nl]=Textuur 4096x4096 8bit srgb -Name[nn]=Tekstur 4096 × 4096 8-bits SRGB +Name[nn]=Tekstur 4096 × 4096 8-bits SRGB Name[pl]=Tekstura 4096x4096 8bit srgb Name[pt]=Textura 4096x4096 8-bits sRGB Name[pt_BR]=Textura 4096x4096 8bits sRGB Name[ru]=Текстура 4096x4096 8 бит srgb Name[sk]=Textúra 4096x4096 8bit srgb Name[sv]=Struktur 4096 x 4096 8-bitar SRGB Name[tr]=Doku 4096x4096 8bit srgb Name[uk]=Текстура 4096⨯4096, 8-бітова, srgb Name[x-test]=xxTexture 4096x4096 8bit srgbxx Name[zh_CN]=4096x4096 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 4096x4096 8位元 srgb Type=Link URL[$e]=.source/Texture4096x40968bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture512x5128bitsrgb.desktop b/krita/data/templates/texture/Texture512x5128bitsrgb.desktop index b3b001ab0e..e1856e2098 100644 --- a/krita/data/templates/texture/Texture512x5128bitsrgb.desktop +++ b/krita/data/templates/texture/Texture512x5128bitsrgb.desktop @@ -1,38 +1,38 @@ [Desktop Entry] Icon=template_texture Name=Texture 512x512 8bit srgb Name[bs]=Tekstura 512x512 8bit srgb Name[ca]=Textura 512x512 de 8 bits amb SRGB Name[ca@valencia]=Textura 512x512 de 8 bits amb SRGB Name[cs]=Textura 512x512 8bit srgb Name[da]=Tekstur 512x512 8bit srgb Name[de]=Textur 512x512 8bit srgb Name[el]=Υφή 512x512 8bit srgb Name[en_GB]=Texture 512x512 8bit srgb Name[es]=Textura 512x512 8bits srgb Name[et]=Tekstuur 512x512 8bit srgb Name[eu]=Ehundura 512x512 8bit sGBU Name[fi]=Pintakuvio 512 × 512 8 bit SRGB Name[fr]=Texture 512x512 8bit srgb Name[gl]=Textura de 512×512 e 8 bits SRGB Name[is]=Efnisáferð 512x512 8bita srgb Name[it]=Trama 512x512 8bit srgb Name[ja]=テクスチャ 512x512 8 ビット sRGB Name[ko]=텍스처 512x512 8비트 srgb Name[nb]=Tekstur 512x512 8bit srgb Name[nl]=Textuur 512x512 8bit srgb -Name[nn]=Tekstur 512 × 512 8-bits SRGB +Name[nn]=Tekstur 512 × 512 8-bits SRGB Name[pl]=Tekstura 512x512 8bit srgb Name[pt]=Textura 512x512 8-bits sRGB Name[pt_BR]=Textura 512x512 8bits sRGB Name[ru]=Текстура 512x512 8 бит srgb Name[sk]=Textúra 512x512 8bit srgb Name[sv]=Struktur 512 x 512 8-bitar SRGB Name[tr]=Doku 512x512 8bit srgb Name[uk]=Текстура 512⨯512, 8-бітова, srgb Name[x-test]=xxTexture 512x512 8bit srgbxx Name[zh_CN]=512x512 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 512x512 8位元 srgb Type=Link URL[$e]=.source/Texture512x5128bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/main.cc b/krita/main.cc index fbdfd20f11..7c006f8d4c 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,560 +1,547 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050900 #include #endif #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif #ifdef Q_OS_WIN namespace { typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if QT_VERSION >= 0x050900 QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #endif #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); -#ifdef Q_OS_WIN - // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP - // might get weird crashes atm. - const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); - preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); - qputenv("QT_ANGLE_PLATFORM", "d3d11"); -#else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); -#endif - const QSurfaceFormat format = - KisOpenGL::selectSurfaceFormat(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); + const KisOpenGL::RendererConfig config = + KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); - if (format.renderableType() == QSurfaceFormat::OpenGLES) { - QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); - } else { - QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); - } - KisOpenGL::setDefaultSurfaceFormat(format); + KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif KisUsageLogger::writeHeader(); if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; #if QT_VERSION >= 0x050900 QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #endif #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index a07a6a1f1a..fb148c294a 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,338 +1,351 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa 디지털 페인팅, 자유로운 창의성 Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

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

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

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

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

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

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

xxKrita is the full-featured digital art studio.xx

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-

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

+

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-

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

+

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://www.krita.org/ https://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. Krita és un estudi de pintura digital ple de funcionalitats. Krita es un completo estudio de dibujo digital. Krita adalah studio pelukisan digital dengan fitur yang lengkap. Krita è uno studio d'arte digitale completo. Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. + Krita er ei funksjonsrik digital teiknestove. Krita jest pełnowymiarowym, cyfrowym studiem artystycznym. O Krita é um estúdio de arte digital completo. + O Krita é um estúdio de pintura digital completo. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx + Krita 是一款功能齐全的数字绘画工作室软件。 Krita 是全功能的數位繪圖工作室。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. Window pemulaian kini juga memberikan kamu kabar terbaru tentang Krita. La finestra di avvio ora fornisce anche le ultime novità su Krita. 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. + Oppstartsvindauget viser no siste nytt om Krita. Okno początkowe teraz wyświetla wieści o Kricie. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx + 启动画面现在可以为你呈现与 Krita 有关的最新资讯。 開始視窗也提供給您關於 Krita 的最新消息。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. Hi ha uns deu motors de pinzell immensament potents. Existen unos diez inmensamente potentes motores de pinceles. Ada lebih dari sepuluh mesin kuas yang sangat manjur. Ci sono oltre dieci motori di pennelli incredibilmente potenti. 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. + Det finst meir enn ti enormt kraftige penselmotorar. Istnieje ponad dziesięć zaawansowanych silników pędzli. Existem mais de dez motores de pincéis extremamente poderosos. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx + 它具备超过十种相当强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren. Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. + Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx + 创建并使用色域蒙版可以为你的图像带来更加一致的观感。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional. Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. + Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx + 喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas. Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. + Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok. Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx + 不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics KDE krita org.kde.krita.desktop
diff --git a/krita/pics/misc-dark/dark_snapshot-load.svg b/krita/pics/misc-dark/dark_snapshot-load.svg new file mode 100644 index 0000000000..1f0dff8a87 --- /dev/null +++ b/krita/pics/misc-dark/dark_snapshot-load.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + 2019 + + + Timothée Giet + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/krita/pics/misc-dark/misc-dark-icons.qrc b/krita/pics/misc-dark/misc-dark-icons.qrc index f72b960e74..724c9c506b 100644 --- a/krita/pics/misc-dark/misc-dark-icons.qrc +++ b/krita/pics/misc-dark/misc-dark-icons.qrc @@ -1,67 +1,68 @@ dark_draw-eraser.svg dark_geometry.svg dark_ox16-action-object-align-horizontal-center-calligra.svg dark_ox16-action-object-align-vertical-bottom-calligra.svg dark_ox16-action-object-align-vertical-center-calligra.svg dark_ox16-action-object-align-vertical-top-calligra.svg dark_ox16-action-object-order-back-calligra.svg dark_ox16-action-object-order-front-calligra.svg dark_ox16-action-object-order-lower-calligra.svg dark_ox16-action-object-order-raise-calligra.svg dark_ox16-action-object-group-calligra.svg dark_ox16-action-object-ungroup-calligra.svg dark_distribute-horizontal-center.svg dark_distribute-horizontal-left.svg dark_distribute-horizontal-right.svg dark_distribute-horizontal.svg dark_distribute-vertical-bottom.svg dark_distribute-vertical-center.svg dark_distribute-vertical-top.svg dark_distribute-vertical.svg dark_paintop_settings_01.svg dark_paintop_settings_02.svg dark_pivot-point.svg dark_stroke-cap-butt.svg dark_stroke-cap-round.svg dark_stroke-cap-square.svg dark_stroke-join-bevel.svg dark_stroke-join-miter.svg dark_stroke-join-round.svg dark_symmetry-horizontal.svg dark_symmetry-vertical.svg dark_onionOff.svg dark_onionOn.svg dark_onion_skin_options.svg dark_path-break-point.svg dark_path-break-segment.svg dark_pathpoint-corner.svg dark_pathpoint-curve.svg dark_pathpoint-insert.svg dark_pathpoint-join.svg dark_pathpoint-line.svg dark_pathpoint-merge.svg dark_pathpoint-remove.svg dark_pathpoint-smooth.svg dark_pathpoint-symmetric.svg dark_pathsegment-curve.svg dark_pathsegment-line.svg dark_opacity-increase.svg dark_opacity-decrease.svg dark_lightness-increase.svg dark_lightness-decrease.svg dark_brushsize-increase.svg dark_brushsize-decrease.svg dark_curve-preset-u.svg dark_curve-preset-s.svg dark_curve-preset-s-reverse.svg dark_curve-preset-linear.svg dark_curve-preset-linear-reverse.svg dark_curve-preset-l.svg dark_curve-preset-j.svg dark_curve-preset-arch.svg + dark_snapshot-load.svg dark_ox16-action-object-align-horizontal-right-calligra.svg dark_ox16-action-object-align-horizontal-left-calligra.svg diff --git a/krita/pics/misc-light/light_snapshot-load.svg b/krita/pics/misc-light/light_snapshot-load.svg new file mode 100644 index 0000000000..f9ae86e450 --- /dev/null +++ b/krita/pics/misc-light/light_snapshot-load.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + 2019 + + + Timothée Giet + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/krita/pics/misc-light/misc-light-icons.qrc b/krita/pics/misc-light/misc-light-icons.qrc index dc7b2ed33a..9f20f14ba8 100644 --- a/krita/pics/misc-light/misc-light-icons.qrc +++ b/krita/pics/misc-light/misc-light-icons.qrc @@ -1,67 +1,68 @@ light_draw-eraser.svg light_geometry.svg light_ox16-action-object-align-horizontal-center-calligra.svg light_ox16-action-object-align-vertical-bottom-calligra.svg light_ox16-action-object-align-vertical-center-calligra.svg light_ox16-action-object-align-vertical-top-calligra.svg light_ox16-action-object-order-back-calligra.svg light_ox16-action-object-order-front-calligra.svg light_ox16-action-object-order-lower-calligra.svg light_ox16-action-object-order-raise-calligra.svg light_ox16-action-object-group-calligra.svg light_ox16-action-object-ungroup-calligra.svg light_distribute-horizontal-center.svg light_distribute-horizontal-left.svg light_distribute-horizontal-right.svg light_distribute-horizontal.svg light_distribute-vertical-bottom.svg light_distribute-vertical-center.svg light_distribute-vertical-top.svg light_distribute-vertical.svg light_paintop_settings_01.svg light_paintop_settings_02.svg light_pivot-point.svg light_stroke-cap-butt.svg light_stroke-cap-round.svg light_stroke-cap-square.svg light_stroke-join-bevel.svg light_stroke-join-miter.svg light_stroke-join-round.svg light_symmetry-horizontal.svg light_symmetry-vertical.svg light_onionOff.svg light_onionOn.svg light_onion_skin_options.svg light_path-break-point.svg light_path-break-segment.svg light_pathpoint-corner.svg light_pathpoint-curve.svg light_pathpoint-insert.svg light_pathpoint-join.svg light_pathpoint-line.svg light_pathpoint-merge.svg light_pathpoint-remove.svg light_pathpoint-smooth.svg light_pathpoint-symmetric.svg light_pathsegment-curve.svg light_pathsegment-line.svg light_opacity-increase.svg light_opacity-decrease.svg light_lightness-increase.svg light_lightness-decrease.svg light_brushsize-increase.svg light_brushsize-decrease.svg light_curve-preset-u.svg light_curve-preset-s.svg light_curve-preset-s-reverse.svg light_curve-preset-linear.svg light_curve-preset-linear-reverse.svg light_curve-preset-l.svg light_curve-preset-j.svg light_curve-preset-arch.svg + light_snapshot-load.svg light_ox16-action-object-align-horizontal-left-calligra.svg light_ox16-action-object-align-horizontal-right-calligra.svg diff --git a/libs/brush/kis_png_brush.cpp b/libs/brush/kis_png_brush.cpp index e2b5561699..e587b3b230 100644 --- a/libs/brush/kis_png_brush.cpp +++ b/libs/brush/kis_png_brush.cpp @@ -1,164 +1,164 @@ /* * 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 #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()); if (brushTipImage().isGrayscale()) { setBrushType(MASK); setHasColor(false); } else { setBrushType(IMAGE); setHasColor(true); } } 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()) { 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()); + setName(info.completeBaseName()); } QImage image = reader.read(); if (image.isNull()) { dbgKrita << "Could not create image for" << filename() << ". Error:" << reader.errorString(); setValid(false); return false; } setValid(true); if (image.allGray()) { // Make sure brush tips all have a white background QImage base(image.size(), image.format()); if ((int)base.format() < (int)QImage::Format_RGB32) { base = base.convertToFormat(QImage::Format_ARGB32); } QPainter gc(&base); gc.fillRect(base.rect(), Qt::white); gc.drawImage(0, 0, image); gc.end(); QImage converted = base.convertToFormat(QImage::Format_Grayscale8); setBrushTipImage(converted); setBrushType(MASK); setHasColor(false); } else { setBrushTipImage(image); setBrushType(IMAGE); setHasColor(true); } setWidth(brushTipImage().width()); setHeight(brushTipImage().height()); 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_svg_brush.cpp b/libs/brush/kis_svg_brush.cpp index c8469dfad4..8e76f32f6e 100644 --- a/libs/brush/kis_svg_brush.cpp +++ b/libs/brush/kis_svg_brush.cpp @@ -1,132 +1,132 @@ /* * 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_svg_brush.h" #include #include #include #include #include KisSvgBrush::KisSvgBrush(const QString& filename) : KisScalingSizeBrush(filename) { setBrushType(INVALID); setSpacing(0.25); setHasColor(false); } KisSvgBrush::KisSvgBrush(const KisSvgBrush& rhs) : KisScalingSizeBrush(rhs) , m_svg(rhs.m_svg) { } KisBrush* KisSvgBrush::clone() const { return new KisSvgBrush(*this); } bool KisSvgBrush::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 KisSvgBrush::loadFromDevice(QIODevice *dev) { m_svg = dev->readAll(); QSvgRenderer renderer(m_svg); QRect box = renderer.viewBox(); if (box.isEmpty()) return false; QImage image_(1000, (1000 * box.height()) / box.width(), QImage::Format_ARGB32); { QPainter p(&image_); p.fillRect(0, 0, image_.width(), image_.height(), Qt::white); renderer.render(&p); } QVector table; for (int i = 0; i < 256; ++i) table.push_back(qRgb(i, i, i)); image_ = image_.convertToFormat(QImage::Format_Indexed8, table); setBrushTipImage(image_); setValid(true); // Well for now, always true if (brushTipImage().isGrayscale()) { setBrushType(MASK); setHasColor(false); } else { setBrushType(IMAGE); setHasColor(true); } setWidth(brushTipImage().width()); setHeight(brushTipImage().height()); QFileInfo fi(filename()); - setName(fi.baseName()); + setName(fi.completeBaseName()); return !brushTipImage().isNull() && valid(); } bool KisSvgBrush::save() { QFile f(filename()); if (!f.open(QFile::WriteOnly)) return false; bool res = saveToDevice(&f); f.close(); return res; } bool KisSvgBrush::saveToDevice(QIODevice *dev) const { if((dev->write(m_svg.constData(), m_svg.size()) == m_svg.size())) { KoResource::saveToDevice(dev); return true; } return false; } QString KisSvgBrush::defaultFileExtension() const { return QString(".svg"); } void KisSvgBrush::toXML(QDomDocument& d, QDomElement& e) const { predefinedBrushToXML("svg_brush", e); KisBrush::toXML(d, e); } diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index 7511b52524..a3b3ec6bb0 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,251 +1,252 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_SOURCE_DIR}/libs/flake/text ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) +add_subdirectory(resources/tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceProvider.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeControllerBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeFillWrapper.cpp KoShapeFillResourceConnector.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp KisGamutMaskViewConverter.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp commands/KoKeepShapesSelectedCommand.cpp commands/KoPathMergeUtils.cpp html/HtmlSavingContext.cpp html/HtmlWriter.cpp tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoCreateShapesTool.cpp tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp text/KoSvgText.cpp text/KoSvgTextProperties.cpp text/KoSvgTextChunkShape.cpp text/KoSvgTextShape.cpp text/KoSvgTextShapeMarkupConverter.cpp resources/KoSvgSymbolCollectionResource.cpp resources/KoGamutMask.cpp FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KisGamutMaskViewConverter.cpp b/libs/flake/KisGamutMaskViewConverter.cpp index dd499dfe51..0d7a7cf9b8 100644 --- a/libs/flake/KisGamutMaskViewConverter.cpp +++ b/libs/flake/KisGamutMaskViewConverter.cpp @@ -1,175 +1,179 @@ /* * Copyright (c) 2018 Anna Medonosova * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisGamutMaskViewConverter.h" #include #include #include +#include #include //#define DEBUG_GAMUT_MASK_CONVERTER KisGamutMaskViewConverter::KisGamutMaskViewConverter() : m_viewSize(1.0) , m_maskSize(QSizeF(1,1)) , m_maskResolution(1) { computeAndSetZoom(); } KisGamutMaskViewConverter::~KisGamutMaskViewConverter() { } QSize KisGamutMaskViewConverter::viewSize() const { return QSize(m_viewSize, m_viewSize); } QPointF KisGamutMaskViewConverter::documentToView(const QPointF &documentPoint) const { return QPointF(documentToViewX(documentPoint.x()), documentToViewY(documentPoint.y())); } QPointF KisGamutMaskViewConverter::viewToDocument(const QPointF &viewPoint) const { return QPointF(viewToDocumentX(viewPoint.x()), viewToDocumentY(viewPoint.y())); } QRectF KisGamutMaskViewConverter::documentToView(const QRectF &documentRect) const { return QRectF(documentToView(documentRect.topLeft()), documentToView(documentRect.size())); } QRectF KisGamutMaskViewConverter::viewToDocument(const QRectF &viewRect) const { return QRectF(viewToDocument(viewRect.topLeft()), viewToDocument(viewRect.size())); } QSizeF KisGamutMaskViewConverter::documentToView(const QSizeF &documentSize) const { return QSizeF(documentToViewX(documentSize.width()), documentToViewY(documentSize.height())); } QSizeF KisGamutMaskViewConverter::viewToDocument(const QSizeF &viewSize) const { return QSizeF(viewToDocumentX(viewSize.width()), viewToDocumentY(viewSize.height())); } qreal KisGamutMaskViewConverter::documentToViewX(qreal documentX) const { qreal translated = documentX * m_zoomLevel; #ifdef DEBUG_GAMUT_MASK_CONVERTER debugFlake << "KisGamutMaskViewConverter::DocumentToViewX: " << "documentX: " << documentX << " -> translated: " << translated; #endif return translated; } qreal KisGamutMaskViewConverter::documentToViewY(qreal documentY) const { qreal translated = documentY * m_zoomLevel; #ifdef DEBUG_GAMUT_MASK_CONVERTER debugFlake << "KisGamutMaskViewConverter::DocumentToViewY: " << "documentY: " << documentY << " -> translated: " << translated; #endif return translated; } qreal KisGamutMaskViewConverter::viewToDocumentX(qreal viewX) const { qreal translated = viewX / m_zoomLevel; #ifdef DEBUG_GAMUT_MASK_CONVERTER debugFlake << "KisGamutMaskViewConverter::viewToDocumentX: " << "viewX: " << viewX << " -> translated: " << translated; #endif return translated; } qreal KisGamutMaskViewConverter::viewToDocumentY(qreal viewY) const { qreal translated = viewY / m_zoomLevel; #ifdef DEBUG_GAMUT_MASK_CONVERTER debugFlake << "KisGamutMaskViewConverter::viewToDocumentY: " << "viewY: " << viewY << " -> translated: " << translated; #endif return translated; } void KisGamutMaskViewConverter::setZoom(qreal zoom) { if (qFuzzyCompare(zoom, qreal(0.0)) || qFuzzyCompare(zoom, qreal(1.0))) { zoom = 1; } #ifdef DEBUG_GAMUT_MASK_CONVERTER debugFlake << "KisGamutMaskViewConverter::setZoom: setting to " << zoom; #endif m_zoomLevel = zoom; } void KisGamutMaskViewConverter::zoom(qreal *zoomX, qreal *zoomY) const { *zoomX = m_zoomLevel; *zoomY = m_zoomLevel; } void KisGamutMaskViewConverter::setViewSize(QSize viewSize) { m_viewSize = viewSize.width(); computeAndSetZoom(); } void KisGamutMaskViewConverter::setMaskSize(QSizeF maskSize) { + KIS_ASSERT(!qIsNull(maskSize.width())); + KIS_ASSERT(!qIsNull(maskSize.height())); + m_maskSize = maskSize; m_maskResolution = maskSize.width(); computeAndSetZoom(); } void KisGamutMaskViewConverter::computeAndSetZoom() { qreal zoom = m_viewSize / m_maskResolution; #ifdef DEBUG_GAMUT_MASK_CONVERTER debugFlake << "KisGamutMaskViewConverter::computeAndSetZoom: " << "m_viewSize: " << m_viewSize << " m_maskSize: " << m_maskResolution; #endif setZoom(zoom); } diff --git a/libs/flake/KoClipMask.cpp b/libs/flake/KoClipMask.cpp index 8a21648f32..189984e003 100644 --- a/libs/flake/KoClipMask.cpp +++ b/libs/flake/KoClipMask.cpp @@ -1,176 +1,177 @@ /* * 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 "KoClipMask.h" #include #include #include +#include #include #include "kis_algebra_2d.h" #include #include -struct Q_DECL_HIDDEN KoClipMask::Private { - Private() {} +struct Q_DECL_HIDDEN KoClipMask::Private : public QSharedData +{ + Private() + : QSharedData() + {} + Private(const Private &rhs) - : coordinates(rhs.coordinates), - contentCoordinates(rhs.contentCoordinates), - maskRect(rhs.maskRect), - extraShapeTransform(rhs.extraShapeTransform) + : QSharedData() + , coordinates(rhs.coordinates) + , contentCoordinates(rhs.contentCoordinates) + , maskRect(rhs.maskRect) + , extraShapeTransform(rhs.extraShapeTransform) { Q_FOREACH (KoShape *shape, rhs.shapes) { KoShape *clonedShape = shape->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes << clonedShape; } } ~Private() { qDeleteAll(shapes); shapes.clear(); } KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox; KoFlake::CoordinateSystem contentCoordinates = KoFlake::UserSpaceOnUse; QRectF maskRect = QRectF(-0.1, -0.1, 1.2, 1.2); QList shapes; QTransform extraShapeTransform; // TODO: not used anymore, use direct shape transform instead }; KoClipMask::KoClipMask() : m_d(new Private) { } KoClipMask::~KoClipMask() { } -KoClipMask::KoClipMask(const KoClipMask &rhs) - : m_d(new Private(*rhs.m_d)) -{ -} - KoClipMask *KoClipMask::clone() const { return new KoClipMask(*this); } KoFlake::CoordinateSystem KoClipMask::coordinates() const { return m_d->coordinates; } void KoClipMask::setCoordinates(KoFlake::CoordinateSystem value) { m_d->coordinates = value; } KoFlake::CoordinateSystem KoClipMask::contentCoordinates() const { return m_d->contentCoordinates; } void KoClipMask::setContentCoordinates(KoFlake::CoordinateSystem value) { m_d->contentCoordinates = value; } QRectF KoClipMask::maskRect() const { return m_d->maskRect; } void KoClipMask::setMaskRect(const QRectF &value) { m_d->maskRect = value; } QList KoClipMask::shapes() const { return m_d->shapes; } void KoClipMask::setShapes(const QList &value) { m_d->shapes = value; } bool KoClipMask::isEmpty() const { return m_d->shapes.isEmpty(); } void KoClipMask::setExtraShapeOffset(const QPointF &value) { /** * TODO: when we implement source shapes sharing, please wrap the shapes * into a group and apply this transform to the group instead */ if (m_d->contentCoordinates == KoFlake::UserSpaceOnUse) { const QTransform t = QTransform::fromTranslate(value.x(), value.y()); Q_FOREACH (KoShape *shape, m_d->shapes) { shape->applyAbsoluteTransformation(t); } } if (m_d->coordinates == KoFlake::UserSpaceOnUse) { m_d->maskRect.translate(value); } } void KoClipMask::drawMask(QPainter *painter, KoShape *shape) { painter->save(); QPainterPath clipPathInShapeSpace; if (m_d->coordinates == KoFlake::ObjectBoundingBox) { QTransform relativeToShape = KisAlgebra2D::mapToRect(shape->outlineRect()); clipPathInShapeSpace.addPolygon(relativeToShape.map(m_d->maskRect)); } else { clipPathInShapeSpace.addRect(m_d->maskRect); clipPathInShapeSpace = m_d->extraShapeTransform.map(clipPathInShapeSpace); } painter->setClipPath(clipPathInShapeSpace, Qt::IntersectClip); if (m_d->contentCoordinates == KoFlake::ObjectBoundingBox) { QTransform relativeToShape = KisAlgebra2D::mapToRect(shape->outlineRect()); painter->setTransform(relativeToShape, true); } else { painter->setTransform(m_d->extraShapeTransform, true); } KoViewConverter converter; KoShapePainter p; p.setShapes(m_d->shapes); p.paint(*painter, converter); painter->restore(); } diff --git a/libs/flake/KoClipMask.h b/libs/flake/KoClipMask.h index e66af53d35..e2e875d945 100644 --- a/libs/flake/KoClipMask.h +++ b/libs/flake/KoClipMask.h @@ -1,68 +1,66 @@ /* * 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. */ #ifndef KOCLIPMASK_H #define KOCLIPMASK_H #include "kritaflake_export.h" #include -#include #include +#include class KoShape; class QRectF; class QTransform; class QPointF; class QPainter; class KRITAFLAKE_EXPORT KoClipMask { public: KoClipMask(); ~KoClipMask(); KoClipMask *clone() const; KoFlake::CoordinateSystem coordinates() const; void setCoordinates(KoFlake::CoordinateSystem value); KoFlake::CoordinateSystem contentCoordinates() const; void setContentCoordinates(KoFlake::CoordinateSystem value); QRectF maskRect() const; void setMaskRect(const QRectF &value); QList shapes() const; void setShapes(const QList &value); bool isEmpty() const; void setExtraShapeOffset(const QPointF &value); void drawMask(QPainter *painter, KoShape *shape); private: - KoClipMask(const KoClipMask &rhs); - struct Private; - const QScopedPointer m_d; + QSharedDataPointer m_d; }; #endif // KOCLIPMASK_H diff --git a/libs/flake/KoClipPath.cpp b/libs/flake/KoClipPath.cpp index fdb7631c8d..4a2ffe4817 100644 --- a/libs/flake/KoClipPath.cpp +++ b/libs/flake/KoClipPath.cpp @@ -1,240 +1,238 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoClipPath.h" #include "KoPathShape.h" #include "KoViewConverter.h" #include "KoShapeGroup.h" #include #include #include #include +#include #include QTransform scaleToPercent(const QSizeF &size) { const qreal w = qMax(static_cast(1e-5), size.width()); const qreal h = qMax(static_cast(1e-5), size.height()); return QTransform().scale(1.0/w, 1.0/h); } QTransform scaleFromPercent(const QSizeF &size) { const qreal w = qMax(static_cast(1e-5), size.width()); const qreal h = qMax(static_cast(1e-5), size.height()); return QTransform().scale(w/1.0, h/1.0); } -class Q_DECL_HIDDEN KoClipPath::Private +class Q_DECL_HIDDEN KoClipPath::Private : public QSharedData { public: Private() + : QSharedData() {} Private(const Private &rhs) - : clipPath(rhs.clipPath), - clipRule(rhs.clipRule), - coordinates(rhs.coordinates), - initialTransformToShape(rhs.initialTransformToShape), - initialShapeSize(rhs.initialShapeSize) + : QSharedData() + , clipPath(rhs.clipPath) + , clipRule(rhs.clipRule) + , coordinates(rhs.coordinates) + , initialTransformToShape(rhs.initialTransformToShape) + , initialShapeSize(rhs.initialShapeSize) { Q_FOREACH (KoShape *shape, rhs.shapes) { KoShape *clonedShape = shape->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } } ~Private() { qDeleteAll(shapes); shapes.clear(); } void collectShapePath(QPainterPath *result, const KoShape *shape) { if (const KoPathShape *pathShape = dynamic_cast(shape)) { // different shapes add up to the final path using Windind Fill rule (acc. to SVG 1.1) QTransform t = pathShape->absoluteTransformation(0); result->addPath(t.map(pathShape->outline())); } else if (const KoShapeGroup *groupShape = dynamic_cast(shape)) { QList shapes = groupShape->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (const KoShape *child, shapes) { collectShapePath(result, child); } } } void compileClipPath() { QList clipShapes = this->shapes; if (clipShapes.isEmpty()) return; clipPath = QPainterPath(); clipPath.setFillRule(Qt::WindingFill); std::sort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *path, clipShapes) { if (!path) continue; collectShapePath(&clipPath, path); } } QList shapes; QPainterPath clipPath; ///< the compiled clip path in shape coordinates of the clipped shape Qt::FillRule clipRule = Qt::WindingFill; KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox; QTransform initialTransformToShape; ///< initial transformation to shape coordinates of the clipped shape QSizeF initialShapeSize; ///< initial size of clipped shape }; KoClipPath::KoClipPath(QList clipShapes, KoFlake::CoordinateSystem coordinates) : d(new Private()) { d->shapes = clipShapes; d->coordinates = coordinates; d->compileClipPath(); } -KoClipPath::KoClipPath(const KoClipPath &rhs) - : d(new Private(*rhs.d)) -{ -} - KoClipPath::~KoClipPath() { } KoClipPath *KoClipPath::clone() const { return new KoClipPath(*this); } void KoClipPath::setClipRule(Qt::FillRule clipRule) { d->clipRule = clipRule; } Qt::FillRule KoClipPath::clipRule() const { return d->clipRule; } KoFlake::CoordinateSystem KoClipPath::coordinates() const { return d->coordinates; } void KoClipPath::applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter) { QPainterPath clipPath; KoShape *shape = clippedShape; while (shape) { if (shape->clipPath()) { QPainterPath path = shape->clipPath()->path(); QTransform t; if (shape->clipPath()->coordinates() == KoFlake::ObjectBoundingBox) { const QRectF shapeLocalBoundingRect = shape->outline().boundingRect(); t = KisAlgebra2D::mapToRect(shapeLocalBoundingRect) * shape->absoluteTransformation(0); } else { t = shape->absoluteTransformation(0); } path = t.map(path); if (clipPath.isEmpty()) { clipPath = path; } else { clipPath &= path; } } shape = shape->parent(); } if (!clipPath.isEmpty()) { QTransform viewMatrix; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); viewMatrix.scale(zoomX, zoomY); painter.setClipPath(viewMatrix.map(clipPath), Qt::IntersectClip); } } QPainterPath KoClipPath::path() const { return d->clipPath; } QPainterPath KoClipPath::pathForSize(const QSizeF &size) const { return scaleFromPercent(size).map(d->clipPath); } QList KoClipPath::clipPathShapes() const { // TODO: deprecate this method! QList shapes; Q_FOREACH (KoShape *shape, d->shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { shapes << pathShape; } } return shapes; } QList KoClipPath::clipShapes() const { return d->shapes; } QTransform KoClipPath::clipDataTransformation(KoShape *clippedShape) const { if (!clippedShape) return d->initialTransformToShape; // the current transformation of the clipped shape QTransform currentShapeTransform = clippedShape->absoluteTransformation(0); // calculate the transformation which represents any resizing of the clipped shape const QSizeF currentShapeSize = clippedShape->outline().boundingRect().size(); const qreal sx = currentShapeSize.width() / d->initialShapeSize.width(); const qreal sy = currentShapeSize.height() / d->initialShapeSize.height(); QTransform scaleTransform = QTransform().scale(sx, sy); // 1. transform to initial clipped shape coordinates // 2. apply resizing transformation // 3. convert to current clipped shape document coordinates return d->initialTransformToShape * scaleTransform * currentShapeTransform; } diff --git a/libs/flake/KoClipPath.h b/libs/flake/KoClipPath.h index ffa7011dbd..8db2c15b43 100644 --- a/libs/flake/KoClipPath.h +++ b/libs/flake/KoClipPath.h @@ -1,93 +1,91 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCLIPPATH_H #define KOCLIPPATH_H #include "kritaflake_export.h" #include #include +#include #include #include class KoShape; class KoPathShape; class KoViewConverter; class QPainter; class QTransform; class QPainterPath; class QSizeF; /// Clip path used to clip shapes class KRITAFLAKE_EXPORT KoClipPath { public: /** * Create a new shape clipping using the given clip data * @param clipShapes define the clipping shapes, owned by KoClipPath! * @param coordinates shows if ObjectBoundingBox or UserSpaceOnUse coordinate * system is used. */ KoClipPath(QList clipShapes, KoFlake::CoordinateSystem coordinates); ~KoClipPath(); KoClipPath *clone() const; KoFlake::CoordinateSystem coordinates() const; /// Sets the clip rule to be used for the clip path void setClipRule(Qt::FillRule clipRule); /// Returns the current clip rule Qt::FillRule clipRule() const; /// Returns the current clip path with coordinates in percent of the clipped shape size QPainterPath path() const; /// Returns the current clip path scaled to match the specified shape size QPainterPath pathForSize(const QSizeF &size) const; /// Returns the clip path shapes QList clipPathShapes() const; QList clipShapes() const; /** * Returns the transformation from the clip data path shapes to the * current document coordinates of the specified clipped shape. * If the specified clipped shape is null, the transformation * from clip data path shapes to shape coordinates of the clipped shape * at the time of creating this clip path is being returned. */ QTransform clipDataTransformation(KoShape *clippedShape) const; /// Applies the clipping to the given painter static void applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter); -private: - KoClipPath(const KoClipPath &rhs); - private: class Private; - const QScopedPointer d; + QSharedDataPointer d; }; #endif // KOCLIPPATH_H diff --git a/libs/flake/KoColorBackground.cpp b/libs/flake/KoColorBackground.cpp index 903bac80b1..dc9c57f46f 100644 --- a/libs/flake/KoColorBackground.cpp +++ b/libs/flake/KoColorBackground.cpp @@ -1,115 +1,117 @@ /* This file is part of the KDE project * Copyright (C) 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 "KoColorBackground.h" -#include "KoColorBackground_p.h" #include "KoShapeSavingContext.h" #include #include #include #include #include #include -KoColorBackground::KoColorBackground() -: KoShapeBackground(*(new KoColorBackgroundPrivate())) +class KoColorBackground::Private : public QSharedData { -} +public: + Private() + : QSharedData() + , color(Qt::black) + , style(Qt::SolidPattern) + {} -KoColorBackground::KoColorBackground(KoShapeBackgroundPrivate &dd) -: KoShapeBackground(dd) + QColor color; + Qt::BrushStyle style; +}; + +KoColorBackground::KoColorBackground() + : KoShapeBackground() + , d(new Private) { } KoColorBackground::KoColorBackground(const QColor &color, Qt::BrushStyle style) -: KoShapeBackground(*(new KoColorBackgroundPrivate())) + : KoShapeBackground() + , d(new Private) { - Q_D(KoColorBackground); - if (style < Qt::SolidPattern || style >= Qt::LinearGradientPattern) + if (style < Qt::SolidPattern || style >= Qt::LinearGradientPattern) { style = Qt::SolidPattern; + } + d->style = style; d->color = color; } KoColorBackground::~KoColorBackground() { } bool KoColorBackground::compareTo(const KoShapeBackground *other) const { - Q_D(const KoColorBackground); - const KoColorBackground *bg = dynamic_cast(other); return bg && bg->color() == d->color; } QColor KoColorBackground::color() const { - Q_D(const KoColorBackground); return d->color; } void KoColorBackground::setColor(const QColor &color) { - Q_D(KoColorBackground); d->color = color; } Qt::BrushStyle KoColorBackground::style() const { - Q_D(const KoColorBackground); return d->style; } QBrush KoColorBackground::brush() const { - Q_D(const KoColorBackground); return QBrush(d->color, d->style); } void KoColorBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { painter.setBrush(brush()); painter.drawPath(fillPath); } void KoColorBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { - Q_D(KoColorBackground); KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), QBrush(d->color, d->style)); } bool KoColorBackground::loadStyle(KoOdfLoadingContext & context, const QSizeF &) { - Q_D(KoColorBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "solid" || fillStyle == "hatch") { QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.stylesReader()); d->color = brush.color(); d->style = brush.style(); return true; } return false; } diff --git a/libs/flake/KoColorBackground.h b/libs/flake/KoColorBackground.h index fa113db8a8..bef030bb39 100644 --- a/libs/flake/KoColorBackground.h +++ b/libs/flake/KoColorBackground.h @@ -1,68 +1,67 @@ /* This file is part of the KDE project * Copyright (C) 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. */ #ifndef KOCOLORBACKGROUND_H #define KOCOLORBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" #include +#include class KoColorBackgroundPrivate; class QColor; class QBrush; /// A simple solid color shape background class KRITAFLAKE_EXPORT KoColorBackground : public KoShapeBackground { public: KoColorBackground(); /// Creates background from given color and style explicit KoColorBackground(const QColor &color, Qt::BrushStyle style = Qt::SolidPattern); ~KoColorBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Returns the background color QColor color() const; /// Sets the background color void setColor(const QColor &color); /// Returns the background style Qt::BrushStyle style() const; QBrush brush() const; // reimplemented from KoShapeBackground void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; // reimplemented from KoShapeBackground void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; // reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext & context, const QSizeF &shapeSize) override; -protected: - KoColorBackground(KoShapeBackgroundPrivate &dd); private: - Q_DECLARE_PRIVATE(KoColorBackground) - Q_DISABLE_COPY(KoColorBackground) + struct Private; + QSharedDataPointer d; }; #endif // KOCOLORBACKGROUND_H diff --git a/libs/flake/KoColorBackground_p.h b/libs/flake/KoColorBackground_p.h deleted file mode 100644 index 259afa44b0..0000000000 --- a/libs/flake/KoColorBackground_p.h +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 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. - */ - -#ifndef KOCOLORBACKGROUND_P_H -#define KOCOLORBACKGROUND_P_H - -#include "KoShapeBackground_p.h" - -#include - -class KoColorBackgroundPrivate : public KoShapeBackgroundPrivate -{ -public: - KoColorBackgroundPrivate() - : color(Qt::black) - , style(Qt::SolidPattern) - {} - - QColor color; - Qt::BrushStyle style; -}; - - -#endif // KOCOLORBACKGROUND_P_H diff --git a/libs/flake/KoConnectionShape.cpp b/libs/flake/KoConnectionShape.cpp index e8b5c8003a..ae6d46d22a 100644 --- a/libs/flake/KoConnectionShape.cpp +++ b/libs/flake/KoConnectionShape.cpp @@ -1,780 +1,779 @@ /* This file is part of the KDE project * Copyright (C) 2007 Boudewijn Rempt * Copyright (C) 2007,2009 Thorsten Zachmann * Copyright (C) 2007,2009,2010 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 "KoConnectionShape.h" #include "KoConnectionShape_p.h" #include "KoViewConverter.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoConnectionShapeLoadingUpdater.h" #include "KoPathShapeLoader.h" #include "KoPathPoint.h" #include "KoShapeBackground.h" #include #include #include #include #include #include -KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q) - : KoParameterShapePrivate(q), - shape1(0), - shape2(0), - connectionPointId1(-1), - connectionPointId2(-1), - connectionType(KoConnectionShape::Standard), - forceUpdate(false), - hasCustomPath(false) +KoConnectionShape::Private::Private() + : QSharedData() + , shape1(0) + , shape2(0) + , connectionPointId1(-1) + , connectionPointId2(-1) + , connectionType(KoConnectionShape::Standard) + , forceUpdate(false) + , hasCustomPath(false) { } -KoConnectionShapePrivate::KoConnectionShapePrivate(const KoConnectionShapePrivate &rhs, KoConnectionShape *q) - : KoParameterShapePrivate(rhs, q), - path(rhs.path), - shape1(0), // FIXME: it should point to the new shapes!!! - shape2(0), // FIXME: it should point to the new shapes!!! - connectionPointId1(rhs.connectionPointId1), - connectionPointId2(rhs.connectionPointId2), - connectionType(rhs.connectionType), - forceUpdate(rhs.forceUpdate), - hasCustomPath(rhs.hasCustomPath) +KoConnectionShape::Private::Private(const KoConnectionShape::Private &rhs) + : QSharedData() + , path(rhs.path) + , shape1(0) // FIXME: it should point to the new shapes!!! + , shape2(0) // FIXME: it should point to the new shapes!!! + , connectionPointId1(rhs.connectionPointId1) + , connectionPointId2(rhs.connectionPointId2) + , connectionType(rhs.connectionType) + , forceUpdate(rhs.forceUpdate) + , hasCustomPath(rhs.hasCustomPath) { } -QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const +QPointF KoConnectionShape::escapeDirection(int handleId) const { - Q_Q(const KoConnectionShape); QPointF direction; - if (handleConnected(handleId)) { - KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? shape1 : shape2; - int connectionPointId = handleId == KoConnectionShape::StartHandle ? connectionPointId1 : connectionPointId2; + if (d->handleConnected(handleId)) { + KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? d->shape1 : d->shape2; + int connectionPointId = handleId == KoConnectionShape::StartHandle ? d->connectionPointId1 : d->connectionPointId2; KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection; if (ed == KoConnectionPoint::AllDirections) { - QPointF handlePoint = q->shapeToDocument(handles[handleId]); + QPointF handlePoint = shapeToDocument(handles()[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center); /* * Determine the best escape direction from the position of the handle point * and the position and orientation of the attached shape. * The idea is to define 4 sectors, one for each edge of the attached shape. * Each sector starts at the center point of the attached shape and has it * left and right edge going through the two points which define the edge. * Then we check which sector contains our handle point, for which we can * simply calculate the corresponding direction which is orthogonal to the * corresponding bounding box edge. * From that we derive the escape direction from looking at the main coordinate * of the orthogonal direction. */ // define our edge points in the right order const KoFlake::AnchorPosition corners[4] = { KoFlake::BottomRight, KoFlake::BottomLeft, KoFlake::TopLeft, KoFlake::TopRight }; QPointF vHandle = handlePoint-centerPoint; for (int i = 0; i < 4; ++i) { // first point of bounding box edge QPointF p1 = attachedShape->absolutePosition(corners[i]); // second point of bounding box edge QPointF p2 = attachedShape->absolutePosition(corners[(i+1)%4]); // check on which side of the first sector edge our second sector edge is - const qreal c0 = crossProd(p1-centerPoint, p2-centerPoint); + const qreal c0 = d->crossProd(p1-centerPoint, p2-centerPoint); // check on which side of the first sector edge our handle point is - const qreal c1 = crossProd(p1-centerPoint, vHandle); + const qreal c1 = d->crossProd(p1-centerPoint, vHandle); // second edge and handle point must be on the same side of first edge if ((c0 < 0 && c1 > 0) || (c0 > 0 && c1 < 0)) continue; // check on which side of the handle point our second sector edge is - const qreal c2 = crossProd(vHandle, p2-centerPoint); + const qreal c2 = d->crossProd(vHandle, p2-centerPoint); // second edge must be on the same side of the handle point as on first edge if ((c0 < 0 && c2 > 0) || (c0 > 0 && c2 < 0)) continue; // now we found the correct edge QPointF vDir = 0.5 *(p1+p2) - centerPoint; // look at coordinate with the greatest absolute value // and construct our escape direction accordingly const qreal xabs = qAbs(vDir.x()); const qreal yabs = qAbs(vDir.y()); if (xabs > yabs) { direction.rx() = vDir.x() > 0 ? 1.0 : -1.0; direction.ry() = 0.0; } else { direction.rx() = 0.0; direction.ry() = vDir.y() > 0 ? 1.0 : -1.0; } break; } } else if (ed == KoConnectionPoint::HorizontalDirections) { - QPointF handlePoint = q->shapeToDocument(handles[handleId]); + QPointF handlePoint = shapeToDocument(handles()[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center); // use horizontal direction pointing away from center point if (handlePoint.x() < centerPoint.x()) direction = QPointF(-1.0, 0.0); else direction = QPointF(1.0, 0.0); } else if (ed == KoConnectionPoint::VerticalDirections) { - QPointF handlePoint = q->shapeToDocument(handles[handleId]); + QPointF handlePoint = shapeToDocument(handles()[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center); // use vertical direction pointing away from center point if (handlePoint.y() < centerPoint.y()) direction = QPointF(0.0, -1.0); else direction = QPointF(0.0, 1.0); } else if (ed == KoConnectionPoint::LeftDirection) { direction = QPointF(-1.0, 0.0); } else if (ed == KoConnectionPoint::RightDirection) { direction = QPointF(1.0, 0.0); } else if (ed == KoConnectionPoint::UpDirection) { direction = QPointF(0.0, -1.0); } else if (ed == KoConnectionPoint::DownDirection) { direction = QPointF(0.0, 1.0); } // transform escape direction by using our own transformation matrix - QTransform invMatrix = q->absoluteTransformation(0).inverted(); + QTransform invMatrix = absoluteTransformation(0).inverted(); direction = invMatrix.map(direction) - invMatrix.map(QPointF()); direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); } return direction; } -bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect) +bool KoConnectionShape::Private::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect) { qreal sp1 = scalarProd(d1, p2 - p1); if (sp1 < 0.0) return false; qreal sp2 = scalarProd(d2, p1 - p2); if (sp2 < 0.0) return false; // use cross product to check if rays intersects at all qreal cp = crossProd(d1, d2); if (cp == 0.0) { // rays are parallel or coincident if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) { // vertical, coincident isect = 0.5 * (p1 + p2); } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) { // horizontal, coincident isect = 0.5 * (p1 + p2); } else { return false; } } else { // they are intersecting normally isect = p1 + sp1 * d1; } return true; } -QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2) +QPointF KoConnectionShape::Private::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2) { QPointF perpendicular(d1.y(), -d1.x()); qreal sp = scalarProd(perpendicular, p2 - p1); if (sp < 0.0) perpendicular *= -1.0; return perpendicular; } -void KoConnectionShapePrivate::normalPath(const qreal MinimumEscapeLength) +void KoConnectionShape::normalPath(const qreal MinimumEscapeLength) { // Clear the path to build it again. - path.clear(); - path.append(handles[KoConnectionShape::StartHandle]); + d->path.clear(); + d->path.append(handles()[KoConnectionShape::StartHandle]); QList edges1; QList edges2; QPointF direction1 = escapeDirection(KoConnectionShape::StartHandle); QPointF direction2 = escapeDirection(KoConnectionShape::EndHandle); - QPointF edgePoint1 = handles[KoConnectionShape::StartHandle] + MinimumEscapeLength * direction1; - QPointF edgePoint2 = handles[KoConnectionShape::EndHandle] + MinimumEscapeLength * direction2; + QPointF edgePoint1 = handles()[KoConnectionShape::StartHandle] + MinimumEscapeLength * direction1; + QPointF edgePoint2 = handles()[KoConnectionShape::EndHandle] + MinimumEscapeLength * direction2; edges1.append(edgePoint1); edges2.prepend(edgePoint2); - if (handleConnected(KoConnectionShape::StartHandle) && handleConnected(KoConnectionShape::EndHandle)) { + if (d->handleConnected(KoConnectionShape::StartHandle) && d->handleConnected(KoConnectionShape::EndHandle)) { QPointF intersection; // TODO: check if this loop actually ever exits? (DK) while (true) { // first check if directions from current edge points intersect - if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) { + if (d->intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) { // directions intersect, we have another edge point and be done edges1.append(intersection); break; } // check if we are going toward the other handle - qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1); + qreal sp = d->scalarProd(direction1, edgePoint2 - edgePoint1); if (sp >= 0.0) { // if we are having the same direction, go all the way toward // the other handle, else only go half the way if (direction1 == direction2) edgePoint1 += sp * direction1; else edgePoint1 += 0.5 * sp * direction1; edges1.append(edgePoint1); // switch direction - direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); + direction1 = d->perpendicularDirection(edgePoint1, direction1, edgePoint2); } else { // we are not going into the same direction, so switch direction - direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); + direction1 = d->perpendicularDirection(edgePoint1, direction1, edgePoint2); } } } - path.append(edges1); - path.append(edges2); + d->path.append(edges1); + d->path.append(edges2); - path.append(handles[KoConnectionShape::EndHandle]); + d->path.append(handles()[KoConnectionShape::EndHandle]); } -qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2) const +qreal KoConnectionShape::Private::scalarProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.x() + v1.y() * v2.y(); } -qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2) const +qreal KoConnectionShape::Private::crossProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.y() - v1.y() * v2.x(); } -bool KoConnectionShapePrivate::handleConnected(int handleId) const +bool KoConnectionShape::Private::handleConnected(int handleId) const { if (handleId == KoConnectionShape::StartHandle && shape1 && connectionPointId1 >= 0) return true; if (handleId == KoConnectionShape::EndHandle && shape2 && connectionPointId2 >= 0) return true; return false; } void KoConnectionShape::updateConnections() { - Q_D(KoConnectionShape); bool updateHandles = false; if (d->handleConnected(StartHandle)) { if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { // map connection point into our shape coordinates QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position)); - if (d->handles[StartHandle] != p) { - d->handles[StartHandle] = p; + if (handles()[StartHandle] != p) { + handles()[StartHandle] = p; updateHandles = true; } } } if (d->handleConnected(EndHandle)) { if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { // map connection point into our shape coordinates QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position)); - if (d->handles[EndHandle] != p) { - d->handles[EndHandle] = p; + if (handles()[EndHandle] != p) { + handles()[EndHandle] = p; updateHandles = true; } } } if (updateHandles || d->forceUpdate) { update(); // ugly, for repainting the connection we just changed updatePath(QSizeF()); update(); // ugly, for repainting the connection we just changed d->forceUpdate = false; } } KoConnectionShape::KoConnectionShape() - : KoParameterShape(new KoConnectionShapePrivate(this)) + : KoParameterShape() + , d(new Private) { - Q_D(KoConnectionShape); - d->handles.push_back(QPointF(0, 0)); - d->handles.push_back(QPointF(140, 140)); + handles().push_back(QPointF(0, 0)); + handles().push_back(QPointF(140, 140)); - moveTo(d->handles[StartHandle]); - lineTo(d->handles[EndHandle]); + moveTo(handles()[StartHandle]); + lineTo(handles()[EndHandle]); updatePath(QSizeF(140, 140)); clearConnectionPoints(); } KoConnectionShape::KoConnectionShape(const KoConnectionShape &rhs) - : KoParameterShape(new KoConnectionShapePrivate(*rhs.d_func(), this)) + : KoParameterShape(rhs) + , d(rhs.d) { } KoConnectionShape::~KoConnectionShape() { - Q_D(KoConnectionShape); + if (d->shape1) d->shape1->removeDependee(this); if (d->shape2) d->shape2->removeDependee(this); } KoShape *KoConnectionShape::cloneShape() const { return new KoConnectionShape(*this); } void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const { - Q_D(const KoConnectionShape); + context.xmlWriter().startElement("draw:connector"); saveOdfAttributes(context, OdfMandatories | OdfAdditionalAttributes); switch (d->connectionType) { case Lines: context.xmlWriter().addAttribute("draw:type", "lines"); break; case Straight: context.xmlWriter().addAttribute("draw:type", "line"); break; case Curve: context.xmlWriter().addAttribute("draw:type", "curve"); break; default: context.xmlWriter().addAttribute("draw:type", "standard"); break; } if (d->shape1) { context.xmlWriter().addAttribute("draw:start-shape", context.xmlid(d->shape1, "shape", KoElementReference::Counter).toString()); context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointId1); } else { - QPointF p(shapeToDocument(d->handles[StartHandle]) * context.shapeOffset(this)); + QPointF p(shapeToDocument(handles()[StartHandle]) * context.shapeOffset(this)); context.xmlWriter().addAttribute("svg:x1", p.x()); context.xmlWriter().addAttribute("svg:y1", p.y()); } if (d->shape2) { context.xmlWriter().addAttribute("draw:end-shape", context.xmlid(d->shape2, "shape", KoElementReference::Counter).toString()); context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointId2); } else { - QPointF p(shapeToDocument(d->handles[EndHandle]) * context.shapeOffset(this)); + QPointF p(shapeToDocument(handles()[EndHandle]) * context.shapeOffset(this)); context.xmlWriter().addAttribute("svg:x2", p.x()); context.xmlWriter().addAttribute("svg:y2", p.y()); } // write the path data context.xmlWriter().addAttribute("svg:d", toString()); saveOdfAttributes(context, OdfViewbox); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { - Q_D(KoConnectionShape); + loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes); QString type = element.attributeNS(KoXmlNS::draw, "type", "standard"); if (type == "lines") d->connectionType = Lines; else if (type == "line") d->connectionType = Straight; else if (type == "curve") d->connectionType = Curve; else d->connectionType = Standard; // reset connection point indices d->connectionPointId1 = -1; d->connectionPointId2 = -1; // reset connected shapes d->shape1 = 0; d->shape2 = 0; if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) { d->connectionPointId1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt(); QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString()); debugFlake << "references start-shape" << shapeId1 << "at glue-point" << d->connectionPointId1; d->shape1 = context.shapeById(shapeId1); if (d->shape1) { debugFlake << "start-shape was already loaded"; d->shape1->addDependee(this); if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { debugFlake << "connecting to start-shape"; - d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); - debugFlake << "start handle position =" << d->handles[StartHandle]; + handles()[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); + debugFlake << "start handle position =" << handles()[StartHandle]; } } else { debugFlake << "start-shape not loaded yet, deferring connection"; context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First)); } } else { - d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString()))); - d->handles[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString()))); + handles()[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString()))); + handles()[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString()))); } if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) { d->connectionPointId2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt(); QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", ""); debugFlake << "references end-shape " << shapeId2 << "at glue-point" << d->connectionPointId2; d->shape2 = context.shapeById(shapeId2); if (d->shape2) { debugFlake << "end-shape was already loaded"; d->shape2->addDependee(this); if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { debugFlake << "connecting to end-shape"; - d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); - debugFlake << "end handle position =" << d->handles[EndHandle]; + handles()[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); + debugFlake << "end handle position =" << handles()[EndHandle]; } } else { debugFlake << "end-shape not loaded yet, deferring connection"; context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second)); } } else { - d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString()))); - d->handles[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString()))); + handles()[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString()))); + handles()[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString()))); } QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString()); QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts); // TODO apply skew values once we support them // load the path data if there is any d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d"); if (d->hasCustomPath) { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); - if (d->subpaths.size() > 0) { + if (subpaths().size() > 0) { QRectF viewBox = loadOdfViewbox(element); if (viewBox.isEmpty()) { // there should be a viewBox to transform the path data // if there is none, use the bounding rectangle of the parsed path viewBox = outline().boundingRect(); } // convert path to viewbox coordinates to have a bounding rect of (0,0 1x1) // which can later be fitted back into the target rect once we have all // the required information QTransform viewMatrix; viewMatrix.scale(viewBox.width() ? static_cast(1.0) / viewBox.width() : 1.0, viewBox.height() ? static_cast(1.0) / viewBox.height() : 1.0); viewMatrix.translate(-viewBox.left(), -viewBox.top()); - d->map(viewMatrix); + map(viewMatrix); // trigger finishing the connections in case we have all data // otherwise it gets called again once the shapes we are // connected to are loaded } else { d->hasCustomPath = false; } finishLoadingConnection(); } else { d->forceUpdate = true; updateConnections(); } loadText(element, context); return true; } void KoConnectionShape::finishLoadingConnection() { - Q_D(KoConnectionShape); + if (d->hasCustomPath) { const bool loadingFinished1 = d->connectionPointId1 >= 0 ? d->shape1 != 0 : true; const bool loadingFinished2 = d->connectionPointId2 >= 0 ? d->shape2 != 0 : true; if (loadingFinished1 && loadingFinished2) { QPointF p1, p2; if (d->handleConnected(StartHandle)) { if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { p1 = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); } } else { - p1 = d->handles[StartHandle]; + p1 = handles()[StartHandle]; } if (d->handleConnected(EndHandle)) { if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); } } else { - p2 = d->handles[EndHandle]; + p2 = handles()[EndHandle]; } - QPointF relativeBegin = d->subpaths.first()->first()->point(); - QPointF relativeEnd = d->subpaths.last()->last()->point(); + QPointF relativeBegin = subpaths().first()->first()->point(); + QPointF relativeEnd = subpaths().last()->last()->point(); QPointF diffRelative(relativeBegin - relativeEnd); QPointF diffAbsolute(p1 - p2); qreal factorX = diffRelative.x() ? diffAbsolute.x() / diffRelative.x(): 1.0; qreal factorY = diffRelative.y() ? diffAbsolute.y() / diffRelative.y(): 1.0; p1.setX(p1.x() - relativeBegin.x() * factorX); p1.setY(p1.y() - relativeBegin.y() * factorY); p2.setX(p2.x() + (1 - relativeEnd.x()) * factorX); p2.setY(p2.y() + (1 - relativeEnd.y()) * factorY); QRectF targetRect = QRectF(p1, p2).normalized(); // transform the normalized coordinates back to our target rectangle QTransform viewMatrix; viewMatrix.translate(targetRect.x(), targetRect.y()); viewMatrix.scale(targetRect.width(), targetRect.height()); - d->map(viewMatrix); + map(viewMatrix); // pretend we are during a forced update, so normalize() // will not trigger an updateConnections() call d->forceUpdate = true; normalize(); d->forceUpdate = false; } } else { updateConnections(); } } void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); - Q_D(KoConnectionShape); - if (handleId >= d->handles.size()) + + if (handleId >= handles().size()) return; - d->handles[handleId] = point; + handles()[handleId] = point; } void KoConnectionShape::updatePath(const QSizeF &size) { Q_UNUSED(size); - Q_D(KoConnectionShape); + const qreal MinimumEscapeLength = (qreal)20.; clear(); switch (d->connectionType) { case Standard: { - d->normalPath(MinimumEscapeLength); + normalPath(MinimumEscapeLength); if (d->path.count() != 0){ moveTo(d->path[0]); for (int index = 1; index < d->path.count(); ++index) lineTo(d->path[index]); } break; } case Lines: { - QPointF direction1 = d->escapeDirection(0); - QPointF direction2 = d->escapeDirection(d->handles.count() - 1); - moveTo(d->handles[StartHandle]); + QPointF direction1 = escapeDirection(0); + QPointF direction2 = escapeDirection(handles().count() - 1); + moveTo(handles()[StartHandle]); if (! direction1.isNull()) - lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1); + lineTo(handles()[StartHandle] + MinimumEscapeLength * direction1); if (! direction2.isNull()) - lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2); - lineTo(d->handles[EndHandle]); + lineTo(handles()[EndHandle] + MinimumEscapeLength * direction2); + lineTo(handles()[EndHandle]); break; } case Straight: - moveTo(d->handles[StartHandle]); - lineTo(d->handles[EndHandle]); + moveTo(handles()[StartHandle]); + lineTo(handles()[EndHandle]); break; case Curve: // TODO - QPointF direction1 = d->escapeDirection(0); - QPointF direction2 = d->escapeDirection(d->handles.count() - 1); - moveTo(d->handles[StartHandle]); + QPointF direction1 = escapeDirection(0); + QPointF direction2 = escapeDirection(handles().count() - 1); + moveTo(handles()[StartHandle]); if (! direction1.isNull() && ! direction2.isNull()) { - QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1; - QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2; - curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]); + QPointF curvePoint1 = handles()[StartHandle] + 5.0 * MinimumEscapeLength * direction1; + QPointF curvePoint2 = handles()[EndHandle] + 5.0 * MinimumEscapeLength * direction2; + curveTo(curvePoint1, curvePoint2, handles()[EndHandle]); } else { - lineTo(d->handles[EndHandle]); + lineTo(handles()[EndHandle]); } break; } normalize(); } bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId) { - Q_D(KoConnectionShape); + // refuse to connect to a shape that depends on us (e.g. a artistic text shape) if (hasDependee(shape1)) return false; if (shape1) { // check if the connection point does exist if (!shape1->hasConnectionPoint(connectionPointId)) return false; // do not connect to the same connection point twice if (d->shape2 == shape1 && d->connectionPointId2 == connectionPointId) return false; } if (d->shape1) d->shape1->removeDependee(this); d->shape1 = shape1; if (d->shape1) d->shape1->addDependee(this); d->connectionPointId1 = connectionPointId; return true; } bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointId) { - Q_D(KoConnectionShape); + // refuse to connect to a shape that depends on us (e.g. a artistic text shape) if (hasDependee(shape2)) return false; if (shape2) { // check if the connection point does exist if (!shape2->hasConnectionPoint(connectionPointId)) return false; // do not connect to the same connection point twice if (d->shape1 == shape2 && d->connectionPointId1 == connectionPointId) return false; } if (d->shape2) d->shape2->removeDependee(this); d->shape2 = shape2; if (d->shape2) d->shape2->addDependee(this); d->connectionPointId2 = connectionPointId; return true; } KoShape *KoConnectionShape::firstShape() const { - Q_D(const KoConnectionShape); + return d->shape1; } int KoConnectionShape::firstConnectionId() const { - Q_D(const KoConnectionShape); + return d->connectionPointId1; } KoShape *KoConnectionShape::secondShape() const { - Q_D(const KoConnectionShape); + return d->shape2; } int KoConnectionShape::secondConnectionId() const { - Q_D(const KoConnectionShape); + return d->connectionPointId2; } KoConnectionShape::Type KoConnectionShape::type() const { - Q_D(const KoConnectionShape); + return d->connectionType; } void KoConnectionShape::setType(Type connectionType) { - Q_D(KoConnectionShape); + d->connectionType = connectionType; updatePath(size()); } void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape) { - Q_D(KoConnectionShape); + KoTosContainer::shapeChanged(type, shape); // check if we are during a forced update const bool updateIsActive = d->forceUpdate; switch (type) { case PositionChanged: case RotationChanged: case ShearChanged: case ScaleChanged: case GenericMatrixChange: case ParameterChanged: if (isParametricShape() && shape == 0) d->forceUpdate = true; break; case Deleted: if (shape != d->shape1 && shape != d->shape2) return; if (shape == d->shape1) connectFirst(0, -1); if (shape == d->shape2) connectSecond(0, -1); break; case ConnectionPointChanged: if (shape == d->shape1 && !shape->hasConnectionPoint(d->connectionPointId1)) { connectFirst(0, -1); } else if ( shape == d->shape2 && !shape->hasConnectionPoint(d->connectionPointId2)){ connectSecond(0, -1); } else { d->forceUpdate = true; } break; case BackgroundChanged: { // connection shape should not have a background QSharedPointer fill = background(); if (fill) { setBackground(QSharedPointer(0)); } return; } default: return; } // the connection was moved while it is connected to some other shapes const bool connectionChanged = !shape && (d->shape1 || d->shape2); // one of the connected shape has moved const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2); if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape()) updateConnections(); // reset the forced update flag d->forceUpdate = false; } QString KoConnectionShape::pathShapeId() const { return KOCONNECTIONSHAPEID; } diff --git a/libs/flake/KoConnectionShape.h b/libs/flake/KoConnectionShape.h index f3f1fa75d5..1bd18948b6 100644 --- a/libs/flake/KoConnectionShape.h +++ b/libs/flake/KoConnectionShape.h @@ -1,145 +1,151 @@ /* This file is part of the KDE project * Copyright (C) 2007 Boudewijn Rempt * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_CONNECTION_SHAPE_H #define KO_CONNECTION_SHAPE_H #include "KoParameterShape.h" #include "kritaflake_export.h" #define KOCONNECTIONSHAPEID "KoConnectionShape" class KoConnectionShapePrivate; /// API docs go here class KRITAFLAKE_EXPORT KoConnectionShape : public KoParameterShape { public: enum Type { Standard, ///< escapes connected shapes with straight lines, connects with perpendicular lines Lines, ///< escapes connected shapes with straight lines, connects with straight line Straight, ///< one straight line between connected shapes Curve ///< a single curved line between connected shapes }; // IDs of the connecting handles enum HandleId { StartHandle, EndHandle, ControlHandle_1, ControlHandle_2, ControlHandle_3 }; KoConnectionShape(); ~KoConnectionShape() override; KoShape* cloneShape() const override; // reimplemented void saveOdf(KoShapeSavingContext &context) const override; // reimplemented bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; // reimplemented QString pathShapeId() const override; /** * Sets the first shape this connector is connected to * * Passing a null pointer as the first parameter will sever the connection. * * @param shape the shape to connect to or null to reset the connection * @param connectionPointId the id of the connection point to connect to * @return true if connection could be established, otherwise false */ bool connectFirst(KoShape *shape, int connectionPointId); /** * Sets the second shape the connector is connected to * * Passing a null pointer as the first parameter will sever the connection. * * @param shape the shape to connect to or null to reset the connection * @param connectionPointId the id of the connection point to connect to * @return true if connection could be established, otherwise false */ bool connectSecond(KoShape *shape, int connectionPointId); /** * Return the first shape this connection is attached to, or null if none. */ KoShape *firstShape() const; /** * Return the connection point id of the first shape we are connected to. * In case we are not connected to a first shape the return value is undefined. * @see firstShape(), KoShape::connectionPoints() */ int firstConnectionId() const; /** * Return the second shape this connection is attached to, or null if none. */ KoShape *secondShape() const; /** * Return the connection point id of the second shape we are connected to. * In case we are not connected to a second shape the return value is undefined. * @see firstShape(), KoShape::connectionPoints() */ int secondConnectionId() const; /** * Finishes the loading of a connection. */ void finishLoadingConnection(); /// Returns connection type Type type() const; /// Sets the connection type void setType(Type connectionType); /// Updates connections to shapes void updateConnections(); protected: KoConnectionShape(const KoConnectionShape &rhs); /// reimplemented void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) override; /// reimplemented void updatePath(const QSizeF &size) override; /// reimplemented void shapeChanged(ChangeType type, KoShape *shape) override; +private: + QPointF escapeDirection(int handleId) const; + + /// Populate the path list by a normal way + void normalPath(const qreal MinimumEscapeLength); private: - Q_DECLARE_PRIVATE(KoConnectionShape) + class Private; + QSharedDataPointer d; }; #endif diff --git a/libs/flake/KoConnectionShape_p.h b/libs/flake/KoConnectionShape_p.h index bb3cd7c4ed..4ae71ee25d 100644 --- a/libs/flake/KoConnectionShape_p.h +++ b/libs/flake/KoConnectionShape_p.h @@ -1,60 +1,54 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCONNECTIONSHAPEPRIVATE_P #define KOCONNECTIONSHAPEPRIVATE_P -#include "KoParameterShape_p.h" +#include +#include "KoConnectionShape.h" -class KoConnectionShapePrivate : public KoParameterShapePrivate +class KoConnectionShape::Private : public QSharedData { public: - explicit KoConnectionShapePrivate(KoConnectionShape *q); - explicit KoConnectionShapePrivate(const KoConnectionShapePrivate &rhs, KoConnectionShape *q); - - /// Returns escape direction of given handle - QPointF escapeDirection(int handleId) const; + explicit Private(); + explicit Private(const Private &rhs); /// Checks if rays from given points into given directions intersect bool intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect); /// Returns perpendicular direction from given point p1 and direction d1 toward point p2 QPointF perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2); - /// Populate the path list by a normal way - void normalPath(const qreal MinimumEscapeLength); - qreal scalarProd(const QPointF &v1, const QPointF &v2) const; qreal crossProd(const QPointF &v1, const QPointF &v2) const; /// Returns if given handle is connected to a shape bool handleConnected(int handleId) const; QList path; KoShape *shape1; KoShape *shape2; int connectionPointId1; int connectionPointId2; KoConnectionShape::Type connectionType; bool forceUpdate; bool hasCustomPath; - Q_DECLARE_PUBLIC(KoConnectionShape) }; #endif diff --git a/libs/flake/KoGradientBackground.cpp b/libs/flake/KoGradientBackground.cpp index 3f69791c82..39d5dadff4 100644 --- a/libs/flake/KoGradientBackground.cpp +++ b/libs/flake/KoGradientBackground.cpp @@ -1,191 +1,183 @@ /* This file is part of the KDE project * Copyright (C) 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 "KoGradientBackground.h" -#include "KoShapeBackground_p.h" #include "KoFlake.h" #include #include #include #include #include #include #include #include #include +#include -class KoGradientBackgroundPrivate : public KoShapeBackgroundPrivate +class KoGradientBackground::Private : public QSharedData { public: - KoGradientBackgroundPrivate() - : gradient(0) + Private() + : QSharedData() + , gradient(0) {} QGradient *gradient; QTransform matrix; }; KoGradientBackground::KoGradientBackground(QGradient * gradient, const QTransform &matrix) - : KoShapeBackground(*(new KoGradientBackgroundPrivate())) + : KoShapeBackground() + , d(new Private) { - Q_D(KoGradientBackground); d->gradient = gradient; d->matrix = matrix; Q_ASSERT(d->gradient); } KoGradientBackground::KoGradientBackground(const QGradient & gradient, const QTransform &matrix) - : KoShapeBackground(*(new KoGradientBackgroundPrivate())) + : KoShapeBackground() + , d(new Private) { - Q_D(KoGradientBackground); d->gradient = KoFlake::cloneGradient(&gradient); d->matrix = matrix; Q_ASSERT(d->gradient); } KoGradientBackground::~KoGradientBackground() { - Q_D(KoGradientBackground); delete d->gradient; } bool KoGradientBackground::compareTo(const KoShapeBackground *other) const { - Q_D(const KoGradientBackground); const KoGradientBackground *otherGradient = dynamic_cast(other); return otherGradient && - d->matrix == otherGradient->d_func()->matrix && - *d->gradient == *otherGradient->d_func()->gradient; + d->matrix == otherGradient->d->matrix && + *d->gradient == *otherGradient->d->gradient; } void KoGradientBackground::setTransform(const QTransform &matrix) { - Q_D(KoGradientBackground); d->matrix = matrix; } QTransform KoGradientBackground::transform() const { - Q_D(const KoGradientBackground); return d->matrix; } void KoGradientBackground::setGradient(const QGradient &gradient) { - Q_D(KoGradientBackground); delete d->gradient; d->gradient = KoFlake::cloneGradient(&gradient); Q_ASSERT(d->gradient); } const QGradient * KoGradientBackground::gradient() const { - Q_D(const KoGradientBackground); return d->gradient; } void KoGradientBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { - Q_D(const KoGradientBackground); if (!d->gradient) return; if (d->gradient->coordinateMode() == QGradient::ObjectBoundingMode) { /** * NOTE: important hack! * * Qt has different notation of QBrush::setTransform() in comparison * to what SVG defines. SVG defines gradientToUser matrix to be postmultiplied * by QBrush::transform(), but Qt does exactly reverse! * * That most probably has beed caused by the fact that Qt uses transposed * matrices and someone just mistyped the stuff long ago :( * * So here we basically emulate this feature by converting the gradient into * QGradient::LogicalMode and doing transformations manually. */ const QRectF boundingRect = fillPath.boundingRect(); QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // TODO: how about slicing the object? QGradient g = *d->gradient; g.setCoordinateMode(QGradient::LogicalMode); QBrush b(g); b.setTransform(d->matrix * gradientToUser); painter.setBrush(b); } else { QBrush b(*d->gradient); b.setTransform(d->matrix); painter.setBrush(b); } painter.drawPath(fillPath); } void KoGradientBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { - Q_D(KoGradientBackground); if (!d->gradient) return; QBrush brush(*d->gradient); brush.setTransform(d->matrix); KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), brush); } bool KoGradientBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { - Q_D(KoGradientBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "gradient") { QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.stylesReader(), shapeSize); const QGradient * gradient = brush.gradient(); if (gradient) { d->gradient = KoFlake::cloneGradient(gradient); d->matrix = brush.transform(); //Gopalakrishna Bhat: If the brush has transparency then we ignore the draw:opacity property and use the brush transparency. // Brush will have transparency if the svg:linearGradient stop point has stop-opacity property otherwise it is opaque if (brush.isOpaque() && styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacityPercent = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacityPercent.isEmpty() && opacityPercent.right(1) == "%") { float opacity = qMin(opacityPercent.left(opacityPercent.length() - 1).toDouble(), 100.0) / 100; QGradientStops stops; Q_FOREACH (QGradientStop stop, d->gradient->stops()) { stop.second.setAlphaF(opacity); stops << stop; } d->gradient->setStops(stops); } } return true; } } return false; } diff --git a/libs/flake/KoGradientBackground.h b/libs/flake/KoGradientBackground.h index 2add15f226..800603108a 100644 --- a/libs/flake/KoGradientBackground.h +++ b/libs/flake/KoGradientBackground.h @@ -1,79 +1,78 @@ /* This file is part of the KDE project * Copyright (C) 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. */ #ifndef KOGRADIENTBACKGROUND_H #define KOGRADIENTBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" #include +#include class QGradient; -class KoGradientBackgroundPrivate; /// A gradient shape background class KRITAFLAKE_EXPORT KoGradientBackground : public KoShapeBackground { public: /** * Creates new gradient background from given gradient. * The background takes ownership of the given gradient. */ explicit KoGradientBackground(QGradient *gradient, const QTransform &matrix = QTransform()); /** * Create new gradient background from the given gradient. * A clone of the given gradient is used. */ explicit KoGradientBackground(const QGradient &gradient, const QTransform &matrix = QTransform()); /// Destroys the background ~KoGradientBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Sets the transform matrix void setTransform(const QTransform &matrix); /// Returns the transform matrix QTransform transform() const; /** * Sets a new gradient. * A clone of the given gradient is used. */ void setGradient(const QGradient &gradient); /// Returns the gradient const QGradient *gradient() const; /// reimplemented from KoShapeBackground void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; /// reimplemented from KoShapeBackground void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; /// reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; - private: - Q_DECLARE_PRIVATE(KoGradientBackground) - Q_DISABLE_COPY(KoGradientBackground) + class Private; + QSharedDataPointer d; }; #endif // KOGRADIENTBACKGROUND_H diff --git a/libs/flake/KoHatchBackground.cpp b/libs/flake/KoHatchBackground.cpp index f556f127b5..844df15973 100644 --- a/libs/flake/KoHatchBackground.cpp +++ b/libs/flake/KoHatchBackground.cpp @@ -1,235 +1,237 @@ /* This file is part of the KDE project * * Copyright (C) 2012 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 "KoHatchBackground.h" -#include "KoColorBackground_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -class KoHatchBackgroundPrivate : public KoColorBackgroundPrivate +class KoHatchBackground::Private : public QSharedData { public: - KoHatchBackgroundPrivate() - : angle(0.0) - , distance(1.0) - , style(KoHatchBackground::Single) + Private() + : QSharedData() + , angle(0.0) + , distance(1.0) + , style(KoHatchBackground::Single) {} QColor lineColor; int angle; qreal distance; KoHatchBackground::HatchStyle style; QString name; }; KoHatchBackground::KoHatchBackground() - : KoColorBackground(*(new KoHatchBackgroundPrivate())) + : KoColorBackground() + , d(new Private) +{ +} + +KoHatchBackground::~KoHatchBackground() { } void KoHatchBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const { - Q_D(const KoHatchBackground); - if (d->color.isValid()) { + if (color().isValid()) { // paint background color if set by using the color background KoColorBackground::paint(painter, converter, context, fillPath); } const QRectF targetRect = fillPath.boundingRect(); painter.save(); painter.setClipPath(fillPath); QPen pen(d->lineColor); // we set the pen width to 0.5 pt for the hatch. This is not defined in the spec. pen.setWidthF(0.5); painter.setPen(pen); QVector lines; // The different styles are handled by painting the lines multiple times with a different // angel offset as basically it just means we paint the lines also at a different angle. // This are the angle offsets we need to apply to the different lines of a style. // -90 is for single, 0 for the 2nd line in double and -45 for the 3th line in triple. const int angleOffset[] = {-90, 0, -45 }; // The number of loops is defined by the style. int loops = (d->style == Single) ? 1 : (d->style == Double) ? 2 : 3; for (int i = 0; i < loops; ++i) { int angle = d->angle - angleOffset[i]; qreal cosAngle = ::cos(angle/180.0*M_PI); // if cos is nearly 0 the lines are horizontal. Use a special case for that if (qAbs(cosAngle) > 0.00001) { qreal xDiff = tan(angle/180.0*M_PI) * targetRect.height(); // calculate the distance we need to increase x when creating the lines so that the // distance between the lines is also correct for rotated lines. qreal xOffset = qAbs(d->distance / cosAngle); // if the lines go to the right we need to start more to the left. Get the correct start. qreal xStart = 0; while (-xDiff < xStart) { xStart -= xOffset; } // if the lines go to the left we need to stop more at the right. Get the correct end offset qreal xEndOffset = 0; if (xDiff < 0) { while (xDiff < -xEndOffset) { xEndOffset += xOffset; } } // create line objects. lines.reserve(lines.size() + int((targetRect.width() + xEndOffset - xStart) / xOffset) + 1); for (qreal x = xStart; x < targetRect.width() + xEndOffset; x += xOffset) { lines.append(QLineF(x, 0, x + xDiff, targetRect.height())); } } else { // horizontal lines lines.reserve(lines.size() + int(targetRect.height()/d->distance) + 1); for (qreal y = 0; y < targetRect.height(); y += d->distance) { lines.append(QLineF(0, y, targetRect.width(), y)); } } } painter.drawLines(lines); painter.restore(); } void KoHatchBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { - Q_D(KoHatchBackground); - KoGenStyle::Type type = style.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; style.addProperty("draw:fill", "hatch", propertyType); style.addProperty("draw:fill-hatch-name", saveHatchStyle(context), propertyType); - bool fillHatchSolid = d->color.isValid(); + bool fillHatchSolid = color().isValid(); style.addProperty("draw:fill-hatch-solid", fillHatchSolid, propertyType); if (fillHatchSolid) { - style.addProperty("draw:fill-color", d->color.name(), propertyType); + style.addProperty("draw:fill-color", color().name(), propertyType); } } QString KoHatchBackground::saveHatchStyle(KoShapeSavingContext &context) const { - Q_D(const KoHatchBackground); KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/); hatchStyle.addAttribute("draw:display-name", d->name); hatchStyle.addAttribute("draw:color", d->lineColor.name()); hatchStyle.addAttribute("draw:distance", d->distance); hatchStyle.addAttribute("draw:rotation", QString("%1").arg(d->angle * 10)); switch (d->style) { case Single: hatchStyle.addAttribute("draw:style", "single"); break; case Double: hatchStyle.addAttribute("draw:style", "double"); break; case Triple: hatchStyle.addAttribute("draw:style", "triple"); break; } return context.mainStyles().insert(hatchStyle, "hatch"); } bool KoHatchBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { // - Q_D(KoHatchBackground); Q_UNUSED(shapeSize); KoStyleStack &styleStack = context.styleStack(); QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "hatch") { QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name"); debugFlake << " hatch style is :" << style; KoXmlElement* draw = context.stylesReader().drawStyles("hatch")[style]; if (draw) { debugFlake << "Hatch style found for:" << style; QString angle = draw->attributeNS(KoXmlNS::draw, "rotation", QString("0")); if (angle.at(angle.size()-1).isLetter()) { d->angle = KoUnit::parseAngle(angle); } else { // OO saves the angle value without unit and multiplied by a factor of 10 d->angle = int(angle.toInt() / 10); } debugFlake << "angle :" << d->angle; d->name = draw->attributeNS(KoXmlNS::draw, "display-name"); // use 2mm as default, just in case it is not given in a document so we show something sensible. d->distance = KoUnit::parseValue(draw->attributeNS(KoXmlNS::draw, "distance", "2mm")); bool fillHatchSolid = styleStack.property(KoXmlNS::draw, "fill-hatch-solid") == QLatin1String("true"); if (fillHatchSolid) { QString fillColor = styleStack.property(KoXmlNS::draw, "fill-color"); if (!fillColor.isEmpty()) { - d->color.setNamedColor(fillColor); + QColor c = color(); + c.setNamedColor(fillColor); + setColor(c); } else { - d->color =QColor(); + setColor(QColor()); } } else { - d->color = QColor(); + setColor(QColor()); } d->lineColor.setNamedColor(draw->attributeNS(KoXmlNS::draw, "color", QString("#000000"))); QString style = draw->attributeNS(KoXmlNS::draw, "style", QString()); if (style == "double") { d->style = Double; } else if (style == "triple") { d->style = Triple; } else { d->style = Single; } } return true; } return false; } diff --git a/libs/flake/KoHatchBackground.h b/libs/flake/KoHatchBackground.h index 872616a488..e4d09690e5 100644 --- a/libs/flake/KoHatchBackground.h +++ b/libs/flake/KoHatchBackground.h @@ -1,58 +1,58 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOHATCHBACKGROUND_H #define KOHATCHBACKGROUND_H #include "KoColorBackground.h" -class KoHatchBackgroundPrivate; - /** * A hatch shape background */ class KoHatchBackground : public KoColorBackground { public: enum HatchStyle { Single, Double, Triple }; KoHatchBackground(); + ~KoHatchBackground() override; // reimplemented void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; // reimplemented void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; // reimplemented bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; private: QString saveHatchStyle(KoShapeSavingContext &context) const; - Q_DECLARE_PRIVATE(KoHatchBackground) - Q_DISABLE_COPY(KoHatchBackground) +private: + class Private; + QSharedDataPointer d; }; #endif /* KOHATCHBACKGROUND_H */ diff --git a/libs/flake/KoOdfGradientBackground.cpp b/libs/flake/KoOdfGradientBackground.cpp index 20f7aa727d..68cb015e5a 100644 --- a/libs/flake/KoOdfGradientBackground.cpp +++ b/libs/flake/KoOdfGradientBackground.cpp @@ -1,386 +1,379 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoOdfGradientBackground.h" -#include "KoShapeBackground_p.h" #include "KoShapeSavingContext.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include "FlakeDebug.h" -class KoOdfGradientBackgroundPrivate : public KoShapeBackgroundPrivate +class KoOdfGradientBackground::Private : public QSharedData { public: - KoOdfGradientBackgroundPrivate() - : style() + Private() + : QSharedData() + , style() , cx(0) , cy(0) , startColor() , endColor() , angle(0) , border(0) , opacity(1.0) {} - ~KoOdfGradientBackgroundPrivate() override{}; + ~Private() = default; //data QString style; int cx; int cy; QColor startColor; QColor endColor; qreal angle; qreal border; qreal opacity; }; KoOdfGradientBackground::KoOdfGradientBackground() - : KoShapeBackground(*(new KoOdfGradientBackgroundPrivate())) + : KoShapeBackground() + , d(new Private) { } KoOdfGradientBackground::~KoOdfGradientBackground() { } bool KoOdfGradientBackground::compareTo(const KoShapeBackground *other) const { Q_UNUSED(other); return false; } bool KoOdfGradientBackground::loadOdf(const KoXmlElement& e) { - Q_D(KoOdfGradientBackground); d->style = e.attributeNS(KoXmlNS::draw, "style", QString()); //TODO: support ellipsoid here too if ((d->style != "rectangular") && (d->style != "square")) { return false; } d->cx = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cx", QString()).remove('%')); d->cy = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cy", QString()).remove('%')); d->border = qBound(0.0,0.01 * e.attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble(),1.0); d->startColor = QColor(e.attributeNS(KoXmlNS::draw, "start-color", QString())); d->startColor.setAlphaF((0.01 * e.attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble())); d->endColor = QColor(e.attributeNS(KoXmlNS::draw, "end-color", QString())); d->endColor.setAlphaF(0.01 * e.attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); d->angle = e.attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10; return true; } void KoOdfGradientBackground::saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const { - Q_D(const KoOdfGradientBackground); - KoGenStyle::Type type = styleFill.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; KoGenStyle gradientStyle(KoGenStyle::GradientStyle); gradientStyle.addAttribute("draw:style", d->style); // draw:style="square" gradientStyle.addAttribute("draw:cx", QString("%1%").arg(d->cx)); gradientStyle.addAttribute("draw:cy", QString("%1%").arg(d->cy)); gradientStyle.addAttribute("draw:start-color", d->startColor.name()); gradientStyle.addAttribute("draw:end-color", d->endColor.name()); gradientStyle.addAttribute("draw:start-intensity", QString("%1%").arg(qRound(d->startColor.alphaF() * 100)) ); gradientStyle.addAttribute("draw:end-intensity", QString("%1%").arg(qRound(d->endColor.alphaF() * 100)) ); gradientStyle.addAttribute("draw:angle", QString("%1").arg(d->angle * 10)); gradientStyle.addAttribute("draw:border", QString("%1%").arg(qRound(d->border * 100.0))); QString gradientStyleName = mainStyles.insert(gradientStyle, "gradient"); styleFill.addProperty("draw:fill", "gradient", propertyType); styleFill.addProperty("draw:fill-gradient-name", gradientStyleName, propertyType); if (d->opacity <= 1.0) { styleFill.addProperty("draw:opacity", QString("%1%").arg(d->opacity * 100.0), propertyType); } } void KoOdfGradientBackground::paint(QPainter& painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath& fillPath) const { - Q_D(const KoOdfGradientBackground); - QImage buffer; QRectF targetRect = fillPath.boundingRect(); QRectF pixels = painter.transform().mapRect(QRectF(0,0,targetRect.width(), targetRect.height())); QSize currentSize( qCeil(pixels.size().width()), qCeil(pixels.size().height()) ); if (buffer.isNull() || buffer.size() != currentSize){ buffer = QImage(currentSize, QImage::Format_ARGB32_Premultiplied); if (d->style == "square") { renderSquareGradient(buffer); } else { renderRectangleGradient(buffer); } } painter.setClipPath(fillPath); painter.setOpacity(d->opacity); painter.drawImage(targetRect, buffer, QRectF(QPointF(0,0), buffer.size())); } void KoOdfGradientBackground::fillStyle(KoGenStyle& style, KoShapeSavingContext& context) { saveOdf(style, context.mainStyles()); } bool KoOdfGradientBackground::loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) { Q_UNUSED(shapeSize); - Q_D(KoOdfGradientBackground); KoStyleStack &styleStack = context.styleStack(); if (!styleStack.hasProperty(KoXmlNS::draw, "fill")) { return false; } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "gradient") { if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacity = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") { d->opacity = qMin(opacity.left(opacity.length() - 1).toDouble(), 100.0) / 100; } } QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name"); KoXmlElement * e = context.stylesReader().drawStyles("gradient")[styleName]; return loadOdf(*e); } return false; } void KoOdfGradientBackground::renderSquareGradient(QImage& buffer) const { - Q_D(const KoOdfGradientBackground); buffer.fill(d->startColor.rgba()); QPainter painter(&buffer); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, false); int width = buffer.width(); int height = buffer.height(); qreal gradientCenterX = qRound(width * d->cx * 0.01); qreal gradientCenterY = qRound(height * d->cy * 0.01); qreal centerX = width * 0.5; qreal centerY = height * 0.5; qreal areaCenterX = qRound(centerX); qreal areaCenterY = qRound(centerY); QTransform m; m.translate(gradientCenterX, gradientCenterY); m.rotate(-d->angle); m.scale(1.0 - d->border, 1.0 - d->border); m.translate(-gradientCenterX, -gradientCenterY); m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); painter.setTransform(m); QLinearGradient linearGradient; linearGradient.setColorAt(1, d->startColor); linearGradient.setColorAt(0, d->endColor); // from center going North linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(centerX, 0); painter.setBrush(linearGradient); painter.drawRect(0, 0, width, centerY); // from center going South linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(0, centerY, width, centerY); // clip the East and West portion QPainterPath clip; clip.moveTo(width, 0); clip.lineTo(width, height); clip.lineTo(0, 0); clip.lineTo(0, height); clip.closeSubpath(); painter.setClipPath(clip); // from center going East linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(centerX, 0, width, height); // from center going West linearGradient.setFinalStop( 0, centerY); painter.setBrush(linearGradient); painter.drawRect(0, 0, centerX, height); } void KoOdfGradientBackground::renderRectangleGradient(QImage& buffer) const { - Q_D(const KoOdfGradientBackground); buffer.fill(d->startColor.rgba()); QPainter painter(&buffer); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, false); int width = buffer.width(); int height = buffer.height(); qreal gradientCenterX = qRound(width * d->cx * 0.01); qreal gradientCenterY = qRound(height * d->cy * 0.01); qreal centerX = width * 0.5; qreal centerY = height * 0.5; qreal areaCenterY = qRound(centerY); qreal areaCenterX = qRound(centerX); QTransform m; m.translate(gradientCenterX, gradientCenterY); // m.rotate(-d->angle); // OOo rotates the gradient differently m.scale(1.0 - d->border, 1.0 - d->border); m.translate(-gradientCenterX, -gradientCenterY); m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); painter.setTransform(m); QLinearGradient linearGradient; linearGradient.setColorAt(1, d->startColor); linearGradient.setColorAt(0, d->endColor); // render background QPainterPath clipPath; if (width < height) { QRectF west(0,0,centerX, height); QRectF east(centerX, 0, centerX, height); linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(0, centerY); painter.setBrush(linearGradient); painter.drawRect(west); linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(east); QRectF north(0,0,width, centerX); QRectF south(0,height - centerX, width, centerX); clipPath.moveTo(0,0); clipPath.lineTo(width, 0); clipPath.lineTo(centerX, centerX); clipPath.closeSubpath(); clipPath.moveTo(width, height); clipPath.lineTo(0, height); clipPath.lineTo(centerX, south.y()); clipPath.closeSubpath(); linearGradient.setStart(centerX, centerX); linearGradient.setFinalStop(centerX, 0); painter.setClipPath(clipPath); painter.setBrush(linearGradient); painter.drawRect(north); linearGradient.setStart(centerX, south.y()); linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(south); } else { QRectF north(0,0,width, centerY); QRectF south(0, centerY, width, centerY); linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(centerX, 0); painter.setBrush(linearGradient); painter.drawRect(north); linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(south); QRectF west(0,0,centerY, height); QRectF east(width - centerY, 0, centerY, height); clipPath.moveTo(0,0); clipPath.lineTo(centerY, centerY); clipPath.lineTo(0,height); clipPath.closeSubpath(); clipPath.moveTo(width, height); clipPath.lineTo(east.x(), centerY); clipPath.lineTo(width,0); clipPath.closeSubpath(); linearGradient.setStart(centerY, centerY); linearGradient.setFinalStop(0, centerY); painter.setClipPath(clipPath); painter.setBrush(linearGradient); painter.drawRect(west); linearGradient.setStart(east.x(), centerY); linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(east); } } void KoOdfGradientBackground::debug() const { - Q_D(const KoOdfGradientBackground); debugFlake << "cx,cy: "<< d->cx << d->cy; debugFlake << "style" << d->style; debugFlake << "colors" << d->startColor << d->endColor; debugFlake << "angle:" << d->angle; debugFlake << "border" << d->border; } diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h index d08f237fa5..f5974313aa 100644 --- a/libs/flake/KoOdfGradientBackground.h +++ b/libs/flake/KoOdfGradientBackground.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOODFGRADIENTBACKGROUND_H #define KOODFGRADIENTBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" class QImage; -class KoOdfGradientBackgroundPrivate; #include + +#include class KoGenStyles; class KoGenStyle; /// Gradients from odf that are not native to Qt class KoOdfGradientBackground : public KoShapeBackground { public: // constructor KoOdfGradientBackground(); // destructor ~KoOdfGradientBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// reimplemented from KoShapeBackground void fillStyle(KoGenStyle& style, KoShapeSavingContext& context) override; /// reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) override; /// reimplemented from KoShapeBackground void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const override; private: bool loadOdf(const KoXmlElement &element); void saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const; void renderSquareGradient(QImage &buffer) const; void renderRectangleGradient(QImage &buffer) const; private: void debug() const; - private: - Q_DECLARE_PRIVATE(KoOdfGradientBackground) - Q_DISABLE_COPY(KoOdfGradientBackground) + class Private; + QSharedDataPointer d; }; #endif diff --git a/libs/flake/KoParameterShape.cpp b/libs/flake/KoParameterShape.cpp index a1a03e0101..9b1bd51126 100644 --- a/libs/flake/KoParameterShape.cpp +++ b/libs/flake/KoParameterShape.cpp @@ -1,166 +1,168 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007, 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoParameterShape.h" #include "KoParameterShape_p.h" #include #include #include -KoParameterShapePrivate::KoParameterShapePrivate(KoParameterShape *shape) - : KoPathShapePrivate(shape) +KoParameterShape::Private::Private() + : QSharedData() , parametric(true) { } -KoParameterShapePrivate::KoParameterShapePrivate(const KoParameterShapePrivate &rhs, KoParameterShape *q) - : KoPathShapePrivate(rhs, q) +KoParameterShape::Private::Private(const Private &rhs) + : QSharedData() , parametric(rhs.parametric) , handles(rhs.handles) { } KoParameterShape::KoParameterShape() - : KoPathShape(new KoParameterShapePrivate(this)) + : KoPathShape() + , d(new Private) { } -KoParameterShape::KoParameterShape(KoParameterShapePrivate *dd) - : KoPathShape(dd) +KoParameterShape::KoParameterShape(const KoParameterShape &rhs) + : KoPathShape(rhs) + , d(rhs.d) { } KoParameterShape::~KoParameterShape() { } void KoParameterShape::moveHandle(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers) { - Q_D(KoParameterShape); + if (handleId >= d->handles.size()) { warnFlake << "handleId out of bounds"; return; } update(); // function to do special stuff moveHandleAction(handleId, documentToShape(point), modifiers); updatePath(size()); update(); } int KoParameterShape::handleIdAt(const QRectF & rect) const { - Q_D(const KoParameterShape); + int handle = -1; for (int i = 0; i < d->handles.size(); ++i) { if (rect.contains(d->handles.at(i))) { handle = i; break; } } return handle; } QPointF KoParameterShape::handlePosition(int handleId) const { - Q_D(const KoParameterShape); + return d->handles.value(handleId); } void KoParameterShape::paintHandles(KisHandlePainterHelper &handlesHelper) { - Q_D(KoParameterShape); + QList::const_iterator it(d->handles.constBegin()); for (; it != d->handles.constEnd(); ++it) { handlesHelper.drawGradientHandle(*it); } } void KoParameterShape::paintHandle(KisHandlePainterHelper &handlesHelper, int handleId) { - Q_D(KoParameterShape); + handlesHelper.drawGradientHandle(d->handles[handleId]); } void KoParameterShape::setSize(const QSizeF &newSize) { - Q_D(KoParameterShape); + QTransform matrix(resizeMatrix(newSize)); for (int i = 0; i < d->handles.size(); ++i) { d->handles[i] = matrix.map(d->handles[i]); } KoPathShape::setSize(newSize); } QPointF KoParameterShape::normalize() { - Q_D(KoParameterShape); + QPointF offset(KoPathShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int i = 0; i < d->handles.size(); ++i) { d->handles[i] = matrix.map(d->handles[i]); } return offset; } bool KoParameterShape::isParametricShape() const { - Q_D(const KoParameterShape); + return d->parametric; } void KoParameterShape::setParametricShape(bool parametric) { - Q_D(KoParameterShape); + d->parametric = parametric; update(); } QList KoParameterShape::handles() const { - Q_D(const KoParameterShape); + return d->handles; } void KoParameterShape::setHandles(const QList &handles) { - Q_D(KoParameterShape); + d->handles = handles; - d->shapeChanged(ParameterChanged); + shapeChangedPriv(ParameterChanged); } int KoParameterShape::handleCount() const { - Q_D(const KoParameterShape); + return d->handles.count(); } diff --git a/libs/flake/KoParameterShape.h b/libs/flake/KoParameterShape.h index f20502f9fb..6af45a81cb 100644 --- a/libs/flake/KoParameterShape.h +++ b/libs/flake/KoParameterShape.h @@ -1,162 +1,163 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPARAMETERSHAPE_H #define KOPARAMETERSHAPE_H #include "KoPathShape.h" #include "kritaflake_export.h" class KoParameterShapePrivate; class KisHandlePainterHelper; /** * KoParameterShape is the base class for all parametric shapes * in flake. * Parametric shapes are those whose appearance can be completely * defined by a few numerical parameters. Rectangle, ellipse and star * are examples of parametric shapes. * In flake, these shape parameters can be manipulated visually by means * of control points. These control points can be moved with the mouse * on the canvas which changes the shapes parameter values and hence the * shapes appearance in realtime. * KoParameterShape is derived from the KoPathShape class that means * by changing the shape parameters, the underlying path is manipulated. * A parametric shape can be converted into a path shape by simply calling * the setModified method. This makes the path tool know that it can handle * the shape like a path shape, so that modifying the single path points * is possible. */ class KRITAFLAKE_EXPORT KoParameterShape : public KoPathShape { public: KoParameterShape(); ~KoParameterShape() override; /** * @brief Move handle to point * * This method calls moveHandleAction. Overload moveHandleAction to get the behaviour you want. * After that updatePath and a repaint is called. * * @param handleId the id of the handle to move * @param point the point to move the handle to in document coordinates * @param modifiers the keyboard modifiers used during moving the handle */ void moveHandle(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier); /** * @brief Get the id of the handle within the given rect * * @param rect the rect in shape coordinates * @return id of the found handle or -1 if none was found */ int handleIdAt(const QRectF &rect) const; /** * @brief Get the handle position * * @param handleId the id of the handle for which to get the position in shape coordinates */ QPointF handlePosition(int handleId) const; /** * @brief Paint the handles * * @param handlesHelper the helper of the handles used for painting * @sa KisHandlePainterHelper */ void paintHandles(KisHandlePainterHelper &handlesHelper); /** * @brief Paint the given handles * * @param handlesHelper the helper of the handle used for painting * @param handleId of the handle which should be repainted */ void paintHandle(KisHandlePainterHelper &handlesHelper, int handleId); /// reimplemented from KoShape void setSize(const QSizeF &size) override; /** * @brief Check if object is a parametric shape * * It is no longer a parametric shape when the path was manipulated * * @return true if it is a parametic shape, false otherwise */ bool isParametricShape() const; /** * @brief Set if the shape can be modified using parameters * * After the state is set to false it is no longer possible to work * with parameters on this shape. * * @param parametric the new state * @see isParametricShape */ void setParametricShape(bool parametric); QPointF normalize() override; /// return the number of handles set on the shape int handleCount() const; protected: /** * Get the handle positions for manipulating the parameters. * @see setHandles, handleCount() */ QList handles() const; /** * Set the new handle positions which are used by the user to manipulate the parameters. * @see handles(), handleCount() */ void setHandles(const QList &handles); /// constructor - KoParameterShape(KoParameterShapePrivate *); + KoParameterShape(const KoParameterShape &rhs); /** * @brief Updates the internal state of a KoParameterShape. * * This method is called from moveHandle. * * @param handleId of the handle * @param point to move the handle to in shape coordinates * @param modifiers used during move to point */ virtual void moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) = 0; /** * @brief Update the path of the parameter shape * * @param size of the shape */ virtual void updatePath(const QSizeF &size) = 0; -protected: - Q_DECLARE_PRIVATE(KoParameterShape) +private: + class Private; + QSharedDataPointer d; }; #endif /* KOPARAMETERSHAPE_H */ diff --git a/libs/flake/KoParameterShape_p.h b/libs/flake/KoParameterShape_p.h index 2d7880de52..e9c5c8b8cc 100644 --- a/libs/flake/KoParameterShape_p.h +++ b/libs/flake/KoParameterShape_p.h @@ -1,45 +1,46 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007, 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPARAMETERSHAPE_P_H #define KOPARAMETERSHAPE_P_H #include "kritaflake_export.h" #include -#include "KoPathShape_p.h" #include #include +#include class KoParameterShape; -class KRITAFLAKE_EXPORT KoParameterShapePrivate : public KoPathShapePrivate +class KRITAFLAKE_EXPORT KoParameterShape::Private : public QSharedData { public: - explicit KoParameterShapePrivate(KoParameterShape *shape); - explicit KoParameterShapePrivate(const KoParameterShapePrivate &rhs, KoParameterShape *q); + explicit Private(); + explicit Private(const KoParameterShape::Private &rhs); + virtual ~Private() = default; bool parametric; /// the handles that the user can grab and change QList handles; }; #endif diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index 956fb76188..0bb0fda87c 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1757 +1,1692 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" #include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoViewConverter.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include "KisQPainterStateSaver.h" #include #include #include "kis_global.h" #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } -KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q) - : KoTosContainerPrivate(q), - fillRule(Qt::OddEvenFill), - autoFillMarkers(false) +KoPathShape::Private::Private() + : QSharedData() + , fillRule(Qt::OddEvenFill) + , autoFillMarkers(false) { } -KoPathShapePrivate::KoPathShapePrivate(const KoPathShapePrivate &rhs, KoPathShape *q) - : KoTosContainerPrivate(rhs, q), - fillRule(rhs.fillRule), - markersNew(rhs.markersNew), - autoFillMarkers(rhs.autoFillMarkers) +KoPathShape::Private::Private(const Private &rhs) + : QSharedData() + , fillRule(rhs.fillRule) + , markersNew(rhs.markersNew) + , autoFillMarkers(rhs.autoFillMarkers) { - Q_FOREACH (KoSubpath *subPath, rhs.subpaths) { - KoSubpath *clonedSubPath = new KoSubpath(); - - Q_FOREACH (KoPathPoint *point, *subPath) { - *clonedSubPath << new KoPathPoint(*point, q); - } - - subpaths << clonedSubPath; - } } -QRectF KoPathShapePrivate::handleRect(const QPointF &p, qreal radius) const +QRectF KoPathShape::Private::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } -void KoPathShapePrivate::applyViewboxTransformation(const KoXmlElement &element) +void KoPathShape::Private::applyViewboxTransformation(const KoXmlElement &element) { // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { // load the desired size QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // load the desired position QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); viewMatrix.translate(pos.x(), pos.y()); // transform the path data map(viewMatrix); } } KoPathShape::KoPathShape() - :KoTosContainer(new KoPathShapePrivate(this)) -{ -} - -KoPathShape::KoPathShape(KoPathShapePrivate *dd) - : KoTosContainer(dd) + : KoTosContainer() + , d(new Private) { } KoPathShape::KoPathShape(const KoPathShape &rhs) - : KoTosContainer(new KoPathShapePrivate(*rhs.d_func(), this)) + : KoTosContainer(rhs) + , d(rhs.d) { + KoSubpathList subpaths; + Q_FOREACH (KoSubpath *subPath, rhs.d->subpaths) { + KoSubpath *clonedSubPath = new KoSubpath(); + + Q_FOREACH (KoPathPoint *point, *subPath) { + *clonedSubPath << new KoPathPoint(*point, this); + } + + subpaths << clonedSubPath; + } + d->subpaths = subpaths; } KoPathShape::~KoPathShape() { clear(); } KoShape *KoPathShape::cloneShape() const { return new KoPathShape(*this); } void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const { - Q_D(const KoPathShape); - if (d->subpaths.length() <= 1) { QTransform matrix; matrix.scale(scaleFactor.width(), scaleFactor.height()); QString points; KoSubpath *subPath = d->subpaths.first(); KoSubpath::const_iterator pointIt(subPath->constBegin()); KoPathPoint *currPoint= 0; // iterate over all points for (; pointIt != subPath->constEnd(); ++pointIt) { currPoint = *pointIt; if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { break; } const QPointF p = matrix.map(currPoint->point()); points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); } if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { context.xmlWriter().startElement("draw:contour-polygon"); context.xmlWriter().addAttribute("svg:width", size().width()); context.xmlWriter().addAttribute("svg:height", size().height()); const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); context.xmlWriter().addAttribute("draw:points", points); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); return; } } // if we get here we couldn't save as polygon - let-s try contour-path context.xmlWriter().startElement("draw:contour-path"); saveOdfAttributes(context, OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); } void KoPathShape::saveOdf(KoShapeSavingContext & context) const { - Q_D(const KoPathShape); context.xmlWriter().startElement("draw:path"); saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) { - Q_D(KoPathShape); - // first clear the path data from the default path clear(); if (element.localName() == "contour-polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } close(); } else if (element.localName() == "contour-path") { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); // transform the path data d->map(viewMatrix); } setTransformation(QTransform()); return true; } bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { - Q_D(KoPathShape); loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); // first clear the path data from the default path clear(); if (element.localName() == "line") { QPointF start; start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); QPointF end; end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); moveTo(start); lineTo(end); } else if (element.localName() == "polyline" || element.localName() == "polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } if (element.localName() == "polygon") close(); } else { // path loading KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } d->applyViewboxTransformation(element); QPointF pos = normalize(); setTransformation(QTransform()); if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); loadOdfAttributes(element, context, OdfTransformation); // now that the correct transformation is set up // apply that matrix to the path geometry so that // we don't transform the stroke d->map(transformation()); setTransformation(QTransform()); normalize(); loadText(element, context); return true; } QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { - Q_D(const KoPathShape); - style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth) return KoTosContainer::saveStyle(style, context); } void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) { - Q_D(KoPathShape); KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; } else { d->fillRule = Qt::WindingFill; #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); #endif } QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth); } QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) { QRect viewbox; QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); if (! data.isEmpty()) { data.replace(QLatin1Char(','), QLatin1Char(' ')); const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); if (coordinates.count() == 4) { viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), coordinates.at(2).toInt(), coordinates.at(3).toInt()); } } return viewbox; } void KoPathShape::clear() { - Q_D(KoPathShape); - Q_FOREACH (KoSubpath *subpath, d->subpaths) { Q_FOREACH (KoPathPoint *point, *subpath) delete point; delete subpath; } d->subpaths.clear(); notifyPointsChanged(); } void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { - Q_D(KoPathShape); - KisQPainterStateSaver saver(&painter); applyConversion(painter, converter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, converter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG -void KoPathShapePrivate::paintDebug(QPainter &painter) +void KoPathShape::Private::paintDebug(QPainter &painter) { - Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); int i = 0; QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } -void KoPathShapePrivate::debugPath() const +void KoPathShape::Private::debugPath() const { - Q_Q(const KoPathShape); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper) { - Q_D(KoPathShape); - KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(handlesHelper, KoPathPoint::Node); } } QRectF KoPathShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoPathShape::outline() const { - Q_D(const KoPathShape); - QPainterPath path; Q_FOREACH (KoSubpath * subpath, d->subpaths) { KoPathPoint * lastPoint = subpath->first(); bool activeCP = false; Q_FOREACH (KoPathPoint * currPoint, *subpath) { KoPathPoint::PointProperties currProperties = currPoint->properties(); if (currPoint == subpath->first()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( lastPoint->controlPoint2(), currPoint->controlPoint1(), currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( currPoint->controlPoint2(), firstPoint->controlPoint1(), firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { const QTransform transform = absoluteTransformation(0); /** * First we approximate the insets of the stroke by rendering a fat bezier curve * with width set to the maximum inset of miters and markers. The are swept by this * curve will be a good approximation of the real curve bounding rect. */ qreal outlineSweepWidth = 0; const QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); if (lineBorder) { outlineSweepWidth = lineBorder->lineWidth(); } if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom}); // insets extend outside the shape, but width extends both inside and outside, // so we should multiply insets by 2.0 outlineSweepWidth = std::max({outlineSweepWidth, 2.0 * maxInset, 2.0 * stroke()->strokeMaxMarkersInset(this)}); } QPen pen(Qt::black, outlineSweepWidth); // select round joins and caps to ensure it sweeps exactly // 'outlineSweepWidth' pixels in every possible pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); QRectF bb = transform.map(pathStroke(pen)).boundingRect(); if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite recursion return outlineRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { - Q_D(KoPathShape); QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { - Q_D(KoPathShape); - KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); d->subpaths.push_back(path); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { - Q_D(KoPathShape); if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = d->subpaths.last()->last(); - d->updateLast(&lastPoint); + updateLastPriv(&lastPoint); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { - Q_D(KoPathShape); if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); - d->updateLast(&lastPoint); + updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { - Q_D(KoPathShape); if (d->subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = d->subpaths.last()->last(); - d->updateLast(&lastPoint); + updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { - Q_D(KoPathShape); - if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = d->subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0.0) return pointCnt; sweepAngle = qBound(-360.0, sweepAngle, 360.0); if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good approximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { - Q_D(KoPathShape); if (d->subpaths.empty()) { return; } - d->closeSubpath(d->subpaths.last()); + closeSubpathPriv(d->subpaths.last()); } void KoPathShape::closeMerge() { - Q_D(KoPathShape); if (d->subpaths.empty()) { return; } - d->closeMergeSubpath(d->subpaths.last()); + closeMergeSubpathPriv(d->subpaths.last()); } QPointF KoPathShape::normalize() { - Q_D(KoPathShape); QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); - d->shapeChanged(ContentChanged); + shapeChangedPriv(ContentChanged); return tl; } -void KoPathShapePrivate::map(const QTransform &matrix) +void KoPathShape::Private::map(const QTransform &matrix) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { (*it)->map(matrix); } } } -void KoPathShapePrivate::updateLast(KoPathPoint **lastPoint) +void KoPathShape::updateLastPriv(KoPathPoint **lastPoint) { - Q_Q(KoPathShape); // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath - KoPathPoint *subpathStart = subpaths.last()->first(); + KoPathPoint *subpathStart = d->subpaths.last()->first(); // clone the first point of the subpath... - KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, q); + KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, this); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); - subpaths.push_back(path); + d->subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { - Q_D(const KoPathShape); - QList result; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { - Q_D(const KoPathShape); - QList segments; int subpathCount = d->subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = d->subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { - Q_D(const KoPathShape); - for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) { KoSubpath * subpath = d->subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { - Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { - Q_D(const KoPathShape); KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { - Q_D(const KoPathShape); - int i = 0; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { - Q_D(const KoPathShape); - return d->subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { - Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { - Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); notifyPointsChanged(); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); point->setParent(0); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } notifyPointsChanged(); return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one d->subpaths.insert(pointIndex.first + 1, newSubpath); notifyPointsChanged(); return true; } bool KoPathShape::join(int subpathIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first Q_FOREACH (KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path d->subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; notifyPointsChanged(); return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= d->subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; d->subpaths.removeAt(oldSubpathIndex); d->subpaths.insert(newSubpathIndex, subpath); notifyPointsChanged(); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); - d->closeSubpath(subpath); + closeSubpathPriv(subpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); notifyPointsChanged(); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { - Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) { Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.removeAt(subpathIndex); } notifyPointsChanged(); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { - Q_D(KoPathShape); - if (subpathIndex < 0 || subpathIndex > d->subpaths.size()) return false; Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.insert(subpathIndex, subpath); notifyPointsChanged(); return true; } int KoPathShape::combine(KoPathShape *path) { - Q_D(KoPathShape); int insertSegmentPosition = -1; if (!path) return insertSegmentPosition; QTransform pathMatrix = path->absoluteTransformation(0); QTransform myMatrix = absoluteTransformation(0).inverted(); - Q_FOREACH (KoSubpath* subpath, path->d_func()->subpaths) { + Q_FOREACH (KoSubpath* subpath, path->d->subpaths) { KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, this); newPoint->map(pathMatrix); newPoint->map(myMatrix); newSubpath->append(newPoint); } d->subpaths.append(newSubpath); if (insertSegmentPosition < 0) { insertSegmentPosition = d->subpaths.size() - 1; } } normalize(); notifyPointsChanged(); return insertSegmentPosition; } bool KoPathShape::separate(QList & separatedPaths) { - Q_D(KoPathShape); - if (! d->subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(0); Q_FOREACH (KoSubpath* subpath, d->subpaths) { KoPathShape *shape = new KoPathShape(); shape->setStroke(stroke()); shape->setBackground(background()); shape->setShapeId(shapeId()); shape->setZIndex(zIndex()); KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, shape); newPoint->map(myMatrix); newSubpath->append(newPoint); } - shape->d_func()->subpaths.append(newSubpath); + shape->d->subpaths.append(newSubpath); shape->normalize(); // NOTE: shape cannot have any listeners yet, so no notification about // points modification is needed separatedPaths.append(shape); } return true; } -void KoPathShapePrivate::closeSubpath(KoSubpath *subpath) +void KoPathShape::closeSubpathPriv(KoSubpath *subpath) { - Q_Q(KoPathShape); - if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); - q->notifyPointsChanged(); + notifyPointsChanged(); } -void KoPathShapePrivate::closeMergeSubpath(KoSubpath *subpath) +void KoPathShape::closeMergeSubpathPriv(KoSubpath *subpath) { - Q_Q(KoPathShape); - if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); - q->notifyPointsChanged(); + notifyPointsChanged(); } else { - closeSubpath(subpath); + closeSubpathPriv(subpath); } } -KoSubpath *KoPathShapePrivate::subPath(int subpathIndex) const +const KoSubpathList &KoPathShape::subpaths() const +{ + return d->subpaths; +} + +KoSubpathList &KoPathShape::subpaths() +{ + return d->subpaths; +} + +void KoPathShape::map(const QTransform &matrix) +{ + return d->map(matrix); +} + +KoSubpath *KoPathShape::Private::subPath(int subpathIndex) const { if (subpathIndex < 0 || subpathIndex >= subpaths.size()) return 0; return subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { - Q_D(const KoPathShape); - QString pathString; // iterate over all subpaths KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); if (!currPoint) { qWarning() << "Found a zero point in the shape's path!"; continue; } // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); pathString += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); pathString += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } pathString += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return pathString; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } -QString KoPathShapePrivate::nodeTypes() const +QString KoPathShape::Private::nodeTypes() const { QString types; KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } -void KoPathShapePrivate::loadNodeTypes(const KoXmlElement &element) +void KoPathShape::Private::loadNodeTypes(const KoXmlElement &element) { if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { - Q_D(const KoPathShape); return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { - Q_D(KoPathShape); d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->setShapeId(KoPathShapeId); //shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! shadow()) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos) { - Q_D(KoPathShape); - if (!marker && d->markersNew.contains(pos)) { d->markersNew.remove(pos); } else { d->markersNew[pos] = marker; } } KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const { - Q_D(const KoPathShape); return d->markersNew[pos].data(); } bool KoPathShape::hasMarkers() const { - Q_D(const KoPathShape); return !d->markersNew.isEmpty(); } bool KoPathShape::autoFillMarkers() const { - Q_D(const KoPathShape); return d->autoFillMarkers; } void KoPathShape::setAutoFillMarkers(bool value) { - Q_D(KoPathShape); d->autoFillMarkers = value; } void KoPathShape::recommendPointSelectionChange(const QList &newSelection) { - Q_D(KoShape); - - Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { + Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->recommendPointSelectionChange(this, newSelection); } } } void KoPathShape::notifyPointsChanged() { - Q_D(KoShape); - - Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { + Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->notifyPathPointsChanged(this); } } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { - Q_D(const KoPathShape); - if (d->subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); QPair firstSegments; QPair lastSegments; // TODO: these variables are never(!) initialized! KoPathPoint *firstPoint = 0; KoPathPoint *lastPoint = 0; KoPathPoint *secondPoint = 0; KoPathPoint *preLastPoint = 0; KoSubpath *firstSubpath = d->subpaths.first(); stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); // shortent the path to make it look nice // replace the point temporarily in case there is an arrow // BE AWARE: this changes the content of the path so that outline give the correct values. if (firstPoint) { firstSubpath->first() = firstSegments.second.first(); if (secondPoint) { (*firstSubpath)[1] = firstSegments.second.second(); } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = lastSegments.first.first(); } firstSubpath->last() = lastSegments.first.second(); } QPainterPath path = stroker.createStroke(outline()); if (firstPoint) { firstSubpath->first() = firstPoint; if (secondPoint) { (*firstSubpath)[1] = secondPoint; } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = preLastPoint; } firstSubpath->last() = lastPoint; } pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } diff --git a/libs/flake/KoPathShape.h b/libs/flake/KoPathShape.h index 8a79086661..a8c1b41d2a 100644 --- a/libs/flake/KoPathShape.h +++ b/libs/flake/KoPathShape.h @@ -1,523 +1,534 @@ /* This file is part of the KDE project Copyright (C) 2006, 2011 Thorsten Zachmann Copyright (C) 2007,2009 Thomas Zander Copyright (C) 2006-2008 Jan Hambrecht Copyright (C) 2011 Jean-Nicolas Artaud This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPATHSHAPE_H #define KOPATHSHAPE_H #include "kritaflake_export.h" #include #include #include "KoTosContainer.h" #define KoPathShapeId "KoPathShape" class KoPathSegment; class KoPathPoint; class KoPathShapePrivate; class KoMarker; class KisHandlePainterHelper; typedef QPair KoPathPointIndex; /// a KoSubpath contains a path from a moveTo until a close or a new moveTo typedef QList KoSubpath; typedef QList KoSubpathList; /// The position of a path point within a path shape /** * @brief This is the base for all graphical objects. * * All graphical objects are based on this object e.g. lines, rectangulars, pies * and so on. * * The KoPathShape uses KoPathPoint's to describe the path of the shape. * * Here a short example: * 3 points connected by a curveTo's described by the following svg: * M 100,200 C 100,100 250,100 250,200 C 250,200 400,300 400,200. * * This will be stored in 3 KoPathPoint's as * The first point contains in * point 100,200 * controlPoint2 100,100 * The second point contains in * point 250,200 * controlPoint1 250,100 * controlPoint2 250,300 * The third point contains in * point 400,300 * controlPoint1 400,200 * * Not the segments are stored but the points. Out of the points the segments are * generated. See the outline method. The reason for storing it like that is that * it is the points that are modified by the user and not the segments. */ class KRITAFLAKE_EXPORT KoPathShape : public KoTosContainer { public: /** * @brief constructor */ KoPathShape(); /** * @brief */ ~KoPathShape() override; KoShape *cloneShape() const override; /// reimplemented void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override; virtual void paintPoints(KisHandlePainterHelper &handlesHelper); /// reimplemented QRectF outlineRect() const override; /// reimplemented QPainterPath outline() const override; /// reimplemented QRectF boundingRect() const override; /// reimplemented QSizeF size() const override; QPainterPath pathStroke(const QPen &pen) const; /** * Resize the shape * * This makes sure that the pathshape will not be resized to 0 if the new size * is null as that makes it impossible to undo the change. * * All functions that overwrite this function should also use the resizeMatrix * function to get and use the same data in resizing. * * @see resizeMatrix() */ void setSize(const QSizeF &size) override; /// reimplemented bool hitTest(const QPointF &position) const override; // reimplemented void saveOdf(KoShapeSavingContext &context) const override; // reimplemented bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; // basically the same as loadOdf but adapted to the contour cases // tag needs to be either contour-polygon or contour-path from another shape bool loadContourOdf(const KoXmlElement & element, KoShapeLoadingContext &context, const QSizeF &scaleFactor); /** basically the equivalent saveOdf but adapted to the contour cases * @param context the saving context * @param originalSize the original size of the unscaled image. */ void saveContourOdf(KoShapeSavingContext &context, const QSizeF &originalSize) const; /// Removes all subpaths and their points from the path void clear(); /** * @brief Starts a new Subpath * * Moves the pen to p and starts a new subpath. * * @return the newly created point */ KoPathPoint *moveTo(const QPointF &p); /** * @brief Adds a new line segment * * Adds a straight line between the last point and the given point p. * * @return the newly created point */ KoPathPoint *lineTo(const QPointF &p); /** * @brief Adds a new cubic Bezier curve segment. * * Adds a cubic Bezier curve between the last point and the given point p, * using the control points specified by c1 and c2. * * @param c1 control point1 * @param c2 control point2 * @param p the endpoint of this curve segment * * @return The newly created point */ KoPathPoint *curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p); /** * @brief Adds a new quadratic Bezier curve segment. * * Adds a quadratic Bezier curve between the last point and the given point p, * using the control point specified by c. * * @param c control point * @param p the endpoint of this curve segment * * @return The newly created point */ KoPathPoint *curveTo(const QPointF &c, const QPointF &p); /** * @brief Add an arc. * * Adds an arc starting at the current point. The arc will be converted to bezier curves. * * @param rx x radius of the ellipse * @param ry y radius of the ellipse * @param startAngle the angle where the arc will be started * @param sweepAngle the length of the angle * TODO add param to have angle of the ellipse * * @return The newly created point */ KoPathPoint *arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle); /** * @brief Closes the current subpath */ void close(); /** * @brief Closes the current subpath * * It tries to merge the last and first point of the subpath * to one point and then closes the subpath. If merging is not * possible as the two point are to far from each other a close * will be done. * TODO define a maximum distance between the two points until this is working */ void closeMerge(); /** * @brief Normalizes the path data. * * The path points are transformed so that the top-left corner * of the bounding rect is at (0,0). * This should be called after adding points to the path or changing * positions of path points. * @return the offset by which the points are moved in shape coordinates. */ virtual QPointF normalize(); /** * @brief Returns the path points within the given rectangle. * @param rect the rectangle the requested points are in * @return list of points within the rectangle */ QList pointsAt(const QRectF &rect) const; /** * @brief Returns the list of path segments within the given rectangle. * @param rect the rectangle the requested segments are in * @return list of segments within the rectangle */ QList segmentsAt(const QRectF &rect) const; /** * @brief Returns the path point index of a given path point * * @param point the point for which you want to get the index * @return path point index of the point if it exists * otherwise KoPathPointIndex( -1, -1 ) */ KoPathPointIndex pathPointIndex(const KoPathPoint *point) const; /** * @brief Returns the path point specified by a path point index * * @param pointIndex index of the point to get * * @return KoPathPoint on success, 0 otherwise e.g. out of bounds */ KoPathPoint *pointByIndex(const KoPathPointIndex &pointIndex) const; /** * @brief Returns the segment specified by a path point index * * A segment is defined by the point index of the first point in the segment. * A segment contains the defined point and its following point. If the subpath is * closed and the and the pointIndex point to the last point in the subpath, the * following point is the first point in the subpath. * * @param pointIndex index of the first point of the segment * * @return Segment containing both points of the segment or KoPathSegment( 0, 0 ) on error e.g. out of bounds */ KoPathSegment segmentByIndex(const KoPathPointIndex &pointIndex) const; /** * @brief Returns the number of points in the path * * @return The number of points in the path */ int pointCount() const; /** * @brief Returns the number of subpaths in the path * * @return The number of subpaths in the path */ int subpathCount() const; /** * @brief Returns the number of points in a subpath * * @return The number of points in the subpath or -1 if subpath out of bounds */ int subpathPointCount(int subpathIndex) const; /** * @brief Checks if a subpath is closed * * @param subpathIndex index of the subpath to check * * @return true when the subpath is closed, false otherwise */ bool isClosedSubpath(int subpathIndex) const; /** * @brief Inserts a new point into the given subpath at the specified position * * This method keeps the subpath closed if it is closed, and open when it was * open. So it can change the properties of the point inserted. * You might need to update the point before/after to get the desired result * e.g. when you insert the point into a curve. * * @param point to insert * @param pointIndex index at which the point should be inserted * * @return true on success, * false when pointIndex is out of bounds */ bool insertPoint(KoPathPoint *point, const KoPathPointIndex &pointIndex); /** * @brief Removes a point from the path. * * Note that the ownership of the point will pass to the caller. * * @param pointIndex index of the point which should be removed * * @return The removed point on success, * otherwise 0 */ KoPathPoint *removePoint(const KoPathPointIndex &pointIndex); /** * @brief Breaks the path after the point index * * The new subpath will be behind the one that was broken. The segment between * the given point and the one behind will be removed. If you want to split at * one point insert first a copy of the point behind it. * This does not work when the subpath is closed. Use openSubpath for this. * It does not break at the last position of a subpath or if there is only one * point in the subpath. * * @param pointIndex index of the point after which the path should be broken * * @return true if the subpath was broken, otherwise false */ bool breakAfter(const KoPathPointIndex &pointIndex); /** * @brief Joins the given subpath with the following one * * Joins the given subpath with the following one by inserting a segment between * the two subpaths. * This does nothing if the specified subpath is the last subpath * or one of both subpaths is closed. * * @param subpathIndex index of the subpath being joined with the following subpath * * @return true if the subpath was joined, otherwise false */ bool join(int subpathIndex); /** * @brief Moves the position of a subpath within a path * * @param oldSubpathIndex old index of the subpath * @param newSubpathIndex new index of the subpath * * @return true if the subpath was moved, otherwise false e.g. if an index is out of bounds */ bool moveSubpath(int oldSubpathIndex, int newSubpathIndex); /** * @brief Opens a closed subpath * * The subpath is opened by removing the segment before the given point, making * the given point the new start point of the subpath. * * @param pointIndex the index of the point at which to open the closed subpath * @return the new position of the old first point in the subpath * otherwise KoPathPointIndex( -1, -1 ) */ KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); /** * @brief Close a open subpath * * The subpath is closed be inserting a segment between the start and end point, making * the given point the new start point of the subpath. * * @return the new position of the old first point in the subpath * otherwise KoPathPointIndex( -1, -1 ) */ KoPathPointIndex closeSubpath(const KoPathPointIndex &pointIndex); /** * @brief Reverse subpath * * The last point becomes the first point and the first one becomes the last one. * * @param subpathIndex the index of the subpath to reverse */ bool reverseSubpath(int subpathIndex); /** * @brief Removes subpath from the path * @param subpathIndex the index of the subpath to remove * @return the removed subpath on success, 0 otherwise. */ KoSubpath *removeSubpath(int subpathIndex); /** * @brief Adds a subpath at the given index to the path * @param subpath the subpath to add * @param subpathIndex the index at which the new subpath should be inserted * @return true on success, false otherwise e.g. subpathIndex out of bounds */ bool addSubpath(KoSubpath *subpath, int subpathIndex); /** * @brief Combines two path shapes by appending the data of the specified path. * @param path the path to combine with * @return index of the first segment inserted or -1 on failure */ int combine(KoPathShape *path); /** * @brief Creates separate path shapes, one for each existing subpath. * @param separatedPaths the list which contains the separated path shapes * @return true if separating the path was successful, false otherwise */ bool separate(QList &separatedPaths); /** * Returns the specific path shape id. * * Path shape derived shapes have a different shape id which link them * to their respective shape factories. In most cases they do not have * a special tool for editing them. * This function returns the specific shape id for finding the shape * factory from KoShapeRegistry. The default KoPathShapeId is returned * from KoShape::shapeId() so that the generic path editing tool gets * activated when the shape is selected. * * @return the specific shape id */ virtual QString pathShapeId() const; /// Returns a odf/svg string representation of the path data with the given matrix applied. QString toString(const QTransform &matrix = QTransform()) const; /// Returns the fill rule for the path object Qt::FillRule fillRule() const; /// Sets the fill rule to be used for painting the background void setFillRule(Qt::FillRule fillRule); /// Creates path shape from given QPainterPath static KoPathShape *createShapeFromPainterPath(const QPainterPath &path); /// Returns the viewbox from the given xml element. static QRect loadOdfViewbox(const KoXmlElement &element); void setMarker(KoMarker *marker, KoFlake::MarkerPosition pos); KoMarker* marker(KoFlake::MarkerPosition pos) const; bool hasMarkers() const; bool autoFillMarkers() const; void setAutoFillMarkers(bool value); public: struct KRITAFLAKE_EXPORT PointSelectionChangeListener : public ShapeChangeListener { void notifyShapeChanged(ChangeType type, KoShape *shape) override; virtual void recommendPointSelectionChange(KoPathShape *shape, const QList &newSelection) = 0; virtual void notifyPathPointsChanged(KoPathShape *shape) = 0; }; void recommendPointSelectionChange(const QList &newSelection); protected: void notifyPointsChanged(); -private: +protected: /// constructor: to be used in cloneShape(), not in descendants! /// \internal + /// XXX private? KoPathShape(const KoPathShape &rhs); protected: - /// constructor:to be used in descendant shapes - /// \internal - KoPathShape(KoPathShapePrivate *); - /// reimplemented QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override; /// reimplemented void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override; /** * @brief Add an arc. * * Adds an arc starting at the current point. The arc will be converted to bezier curves. * @param rx x radius of the ellipse * @param ry y radius of the ellipse * @param startAngle the angle where the arc will be started * @param sweepAngle the length of the angle * TODO add param to have angle of the ellipse * @param offset to the first point in the arc * @param curvePoints a array which take the curve points, pass a 'QPointF curvePoins[12]'; * * @return number of points created by the curve */ int arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF &offset, QPointF *curvePoints) const; /** * Get the resize matrix * * This makes sure that also if the newSize isNull that there will be a * very small size of 0.000001 pixels */ QTransform resizeMatrix( const QSizeF &newSize ) const; private: - Q_DECLARE_PRIVATE(KoPathShape) + /// close-merges specified subpath + void closeMergeSubpathPriv(KoSubpath *subpath); + /// closes specified subpath + void closeSubpathPriv(KoSubpath *subpath); + void updateLastPriv(KoPathPoint **lastPoint); + +protected: + const KoSubpathList &subpaths() const; + /// XXX: refactor this using setter? + KoSubpathList &subpaths(); + void map(const QTransform &matrix); + +private: + class Private; + QSharedDataPointer d; }; Q_DECLARE_METATYPE(KoPathShape*) #endif /* KOPATHSHAPE_H */ diff --git a/libs/flake/KoPathShape_p.h b/libs/flake/KoPathShape_p.h index 5e5c80c25c..f860054ea6 100644 --- a/libs/flake/KoPathShape_p.h +++ b/libs/flake/KoPathShape_p.h @@ -1,102 +1,94 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPATHSHAPEPRIVATE_H #define KOPATHSHAPEPRIVATE_H #include "KoPathShape.h" -#include "KoTosContainer_p.h" #include "KoMarker.h" -class KoPathShapePrivate : public KoTosContainerPrivate +#include + +class KoPathShape::Private : public QSharedData { public: - explicit KoPathShapePrivate(KoPathShape *q); - explicit KoPathShapePrivate(const KoPathShapePrivate &rhs, KoPathShape *q); + explicit Private(); + explicit Private(const Private &rhs); QRectF handleRect(const QPointF &p, qreal radius) const; /// Applies the viewbox transformation defined in the given element void applyViewboxTransformation(const KoXmlElement &element); void map(const QTransform &matrix); - void updateLast(KoPathPoint **lastPoint); - - /// closes specified subpath - void closeSubpath(KoSubpath *subpath); - /// close-merges specified subpath - void closeMergeSubpath(KoSubpath *subpath); - /** * @brief Saves the node types * * This is inspired by inkscape and uses the same mechanism as they do. * The only difference is that they use sodipodi:nodeTypes as element and * we use calligra:nodeTyes as attribute. * This attribute contains of a string which has the node type of each point * in it. The following node types exist: * * c corner * s smooth * z symmetric * * The first point of a path is always of the type c. * If the path is closed the type of the first point is saved in the last element * E.g. you have a closed path with 2 points in it. The first one (start/end of path) * is symmetric and the second one is smooth that will result in the nodeType="czs" * So if there is a closed sub path the nodeTypes contain one more entry then there * are points. That is due to the first and the last point of a closed sub path get * merged into one when they are on the same position. * * @return The node types as string */ QString nodeTypes() const; /** * @brief Loads node types */ void loadNodeTypes(const KoXmlElement &element); /** * @brief Returns subpath at given index * @param subpathIndex the index of the subpath to return * @return subPath on success, or 0 when subpathIndex is out of bounds */ KoSubpath *subPath(int subpathIndex) const; #ifndef NDEBUG /// \internal void paintDebug(QPainter &painter); /** * @brief print debug information about a the points of the path */ void debugPath() const; #endif Qt::FillRule fillRule; - Q_DECLARE_PUBLIC(KoPathShape) - KoSubpathList subpaths; QMap> markersNew; bool autoFillMarkers; QList pointChangeListeners; }; #endif diff --git a/libs/flake/KoPatternBackground.cpp b/libs/flake/KoPatternBackground.cpp index d98ecf8632..41e8a69dd4 100644 --- a/libs/flake/KoPatternBackground.cpp +++ b/libs/flake/KoPatternBackground.cpp @@ -1,500 +1,482 @@ /* This file is part of the KDE project * Copyright (C) 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 "KoPatternBackground.h" -#include "KoShapeBackground_p.h" #include "KoShapeSavingContext.h" #include "KoImageData.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include -class KoPatternBackgroundPrivate : public KoShapeBackgroundPrivate +class KoPatternBackground::Private : public QSharedData { public: - KoPatternBackgroundPrivate() - : repeat(KoPatternBackground::Tiled) + Private() + : QSharedData() + , repeat(KoPatternBackground::Tiled) , refPoint(KoPatternBackground::TopLeft) , imageCollection(0) , imageData(0) { } - ~KoPatternBackgroundPrivate() override { + ~Private() + { delete imageData; } QSizeF targetSize() const { QSizeF size = imageData->imageSize(); if (targetImageSizePercent.width() > 0.0) size.setWidth(0.01 * targetImageSizePercent.width() * size.width()); else if (targetImageSize.width() > 0.0) size.setWidth(targetImageSize.width()); if (targetImageSizePercent.height() > 0.0) size.setHeight(0.01 * targetImageSizePercent.height() * size.height()); else if (targetImageSize.height() > 0.0) size.setHeight(targetImageSize.height()); return size; } QPointF offsetFromRect(const QRectF &fillRect, const QSizeF &imageSize) const { QPointF offset; switch (refPoint) { case KoPatternBackground::TopLeft: offset = fillRect.topLeft(); break; case KoPatternBackground::Top: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::TopRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::Left: offset.setX(fillRect.left()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Center: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Right: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::BottomLeft: offset.setX(fillRect.left()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::Bottom: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::BottomRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; default: break; } if (refPointOffsetPercent.x() > 0.0) offset += QPointF(0.01 * refPointOffsetPercent.x() * imageSize.width(), 0); if (refPointOffsetPercent.y() > 0.0) offset += QPointF(0, 0.01 * refPointOffsetPercent.y() * imageSize.height()); return offset; } QTransform matrix; KoPatternBackground::PatternRepeat repeat; KoPatternBackground::ReferencePoint refPoint; QSizeF targetImageSize; QSizeF targetImageSizePercent; QPointF refPointOffsetPercent; QPointF tileRepeatOffsetPercent; QPointer imageCollection; KoImageData * imageData; }; // ---------------------------------------------------------------- KoPatternBackground::KoPatternBackground(KoImageCollection *imageCollection) - : KoShapeBackground(*(new KoPatternBackgroundPrivate())) + : KoShapeBackground() + , d(new Private) { - Q_D(KoPatternBackground); d->imageCollection = imageCollection; Q_ASSERT(d->imageCollection); } KoPatternBackground::~KoPatternBackground() { } bool KoPatternBackground::compareTo(const KoShapeBackground *other) const { Q_UNUSED(other); return false; } void KoPatternBackground::setTransform(const QTransform &matrix) { - Q_D(KoPatternBackground); d->matrix = matrix; } QTransform KoPatternBackground::transform() const { - Q_D(const KoPatternBackground); return d->matrix; } void KoPatternBackground::setPattern(const QImage &pattern) { - Q_D(KoPatternBackground); delete d->imageData; if (d->imageCollection) { d->imageData = d->imageCollection->createImageData(pattern); } } void KoPatternBackground::setPattern(KoImageData *imageData) { - Q_D(KoPatternBackground); delete d->imageData; d->imageData = imageData; } QImage KoPatternBackground::pattern() const { - Q_D(const KoPatternBackground); if (d->imageData) return d->imageData->image(); return QImage(); } void KoPatternBackground::setRepeat(PatternRepeat repeat) { - Q_D(KoPatternBackground); d->repeat = repeat; } KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const { - Q_D(const KoPatternBackground); return d->repeat; } KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const { - Q_D(const KoPatternBackground); return d->refPoint; } void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint) { - Q_D(KoPatternBackground); d->refPoint = referencePoint; } QPointF KoPatternBackground::referencePointOffset() const { - Q_D(const KoPatternBackground); return d->refPointOffsetPercent; } void KoPatternBackground::setReferencePointOffset(const QPointF &offset) { - Q_D(KoPatternBackground); qreal ox = qMax(qreal(0.0), qMin(qreal(100.0), offset.x())); qreal oy = qMax(qreal(0.0), qMin(qreal(100.0), offset.y())); d->refPointOffsetPercent = QPointF(ox, oy); } QPointF KoPatternBackground::tileRepeatOffset() const { - Q_D(const KoPatternBackground); return d->tileRepeatOffsetPercent; } void KoPatternBackground::setTileRepeatOffset(const QPointF &offset) { - Q_D(KoPatternBackground); d->tileRepeatOffsetPercent = offset; } QSizeF KoPatternBackground::patternDisplaySize() const { - Q_D(const KoPatternBackground); return d->targetSize(); } void KoPatternBackground::setPatternDisplaySize(const QSizeF &size) { - Q_D(KoPatternBackground); d->targetImageSizePercent = QSizeF(); d->targetImageSize = size; } QSizeF KoPatternBackground::patternOriginalSize() const { - Q_D(const KoPatternBackground); return d->imageData->imageSize(); } void KoPatternBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { - Q_D(const KoPatternBackground); if (! d->imageData) return; painter.save(); if (d->repeat == Tiled) { // calculate scaling of pixmap QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); qreal scaleX = targetSize.width() / imageSize.width(); qreal scaleY = targetSize.height() / imageSize.height(); QRectF targetRect = fillPath.boundingRect(); // undo scaling on target rectangle targetRect.setWidth(targetRect.width() / scaleX); targetRect.setHeight(targetRect.height() / scaleY); // determine pattern offset QPointF offset = d->offsetFromRect(targetRect, imageSize); // create matrix for pixmap scaling QTransform matrix; matrix.scale(scaleX, scaleY); painter.setClipPath(fillPath); painter.setWorldTransform(matrix, true); painter.drawTiledPixmap(targetRect, d->imageData->pixmap(imageSize.toSize()), -offset); } else if (d->repeat == Original) { QRectF sourceRect(QPointF(0, 0), d->imageData->imageSize()); QRectF targetRect(QPoint(0, 0), d->targetSize()); targetRect.moveCenter(fillPath.boundingRect().center()); painter.setClipPath(fillPath); painter.drawPixmap(targetRect, d->imageData->pixmap(sourceRect.size().toSize()), sourceRect); } else if (d->repeat == Stretched) { painter.setClipPath(fillPath); // undo conversion of the scaling so that we can use a nicely scaled image of the correct size qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); zoomX = zoomX ? 1 / zoomX : zoomX; zoomY = zoomY ? 1 / zoomY : zoomY; painter.scale(zoomX, zoomY); QRectF targetRect = converter.documentToView(fillPath.boundingRect()); painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize())); } painter.restore(); } void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { - Q_D(KoPatternBackground); if (! d->imageData) return; switch (d->repeat) { case Original: style.addProperty("style:repeat", "no-repeat"); break; case Tiled: style.addProperty("style:repeat", "repeat"); break; case Stretched: style.addProperty("style:repeat", "stretch"); break; } if (d->repeat == Tiled) { QString refPointId = "top-left"; switch (d->refPoint) { case TopLeft: refPointId = "top-left"; break; case Top: refPointId = "top"; break; case TopRight: refPointId = "top-right"; break; case Left: refPointId = "left"; break; case Center: refPointId = "center"; break; case Right: refPointId = "right"; break; case BottomLeft: refPointId = "bottom-left"; break; case Bottom: refPointId = "bottom"; break; case BottomRight: refPointId = "bottom-right"; break; } style.addProperty("draw:fill-image-ref-point", refPointId); if (d->refPointOffsetPercent.x() > 0.0) style.addProperty("draw:fill-image-ref-point-x", QString("%1%").arg(d->refPointOffsetPercent.x())); if (d->refPointOffsetPercent.y() > 0.0) style.addProperty("draw:fill-image-ref-point-y", QString("%1%").arg(d->refPointOffsetPercent.y())); } if (d->repeat != Stretched) { QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); if (targetSize.height() != imageSize.height()) style.addPropertyPt("draw:fill-image-height", targetSize.height()); if (targetSize.width() != imageSize.width()) style.addPropertyPt("draw:fill-image-width", targetSize.width()); } KoGenStyle patternStyle(KoGenStyle::FillImageStyle /*no family name*/); patternStyle.addAttribute("xlink:show", "embed"); patternStyle.addAttribute("xlink:actuate", "onLoad"); patternStyle.addAttribute("xlink:type", "simple"); patternStyle.addAttribute("xlink:href", context.imageHref(d->imageData)); QString patternStyleName = context.mainStyles().insert(patternStyle, "picture"); style.addProperty("draw:fill", "bitmap"); style.addProperty("draw:fill-image-name", patternStyleName); if (d->imageCollection) { context.addDataCenter(d->imageCollection); } } bool KoPatternBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &) { - Q_D(KoPatternBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle != "bitmap") return false; QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name"); KoXmlElement* e = context.stylesReader().drawStyles("fill-image")[styleName]; if (! e) return false; const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString()); if (href.isEmpty()) return false; delete d->imageData; d->imageData = 0; if (d->imageCollection) { d->imageData = d->imageCollection->createImageData(href, context.store()); } if (! d->imageData) { return false; } // read the pattern repeat style QString style = styleStack.property(KoXmlNS::style, "repeat"); if (style == "stretch") d->repeat = Stretched; else if (style == "no-repeat") d->repeat = Original; else d->repeat = Tiled; if (style != "stretch") { // optional attributes which can override original image size if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height")) { QString height = styleStack.property(KoXmlNS::draw, "fill-image-height"); if (height.endsWith('%')) d->targetImageSizePercent.setHeight(height.remove('%').toDouble()); else d->targetImageSize.setHeight(KoUnit::parseValue(height)); } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) { QString width = styleStack.property(KoXmlNS::draw, "fill-image-width"); if (width.endsWith('%')) d->targetImageSizePercent.setWidth(width.remove('%').toDouble()); else d->targetImageSize.setWidth(KoUnit::parseValue(width)); } } if (style == "repeat") { if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) { // align pattern to the given size QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point"); if (align == "top-left") d->refPoint = TopLeft; else if (align == "top") d->refPoint = Top; else if (align == "top-right") d->refPoint = TopRight; else if (align == "left") d->refPoint = Left; else if (align == "center") d->refPoint = Center; else if (align == "right") d->refPoint = Right; else if (align == "bottom-left") d->refPoint = BottomLeft; else if (align == "bottom") d->refPoint = Bottom; else if (align == "bottom-right") d->refPoint = BottomRight; } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) { QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x"); d->refPointOffsetPercent.setX(pointX.remove('%').toDouble()); } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) { QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y"); d->refPointOffsetPercent.setY(pointY.remove('%').toDouble()); } if (styleStack.hasProperty(KoXmlNS::draw, "tile-repeat-offset")) { QString repeatOffset = styleStack.property(KoXmlNS::draw, "tile-repeat-offset"); QStringList tokens = repeatOffset.split('%'); if (tokens.count() == 2) { QString direction = tokens[1].simplified(); if (direction == "horizontal") d->tileRepeatOffsetPercent.setX(tokens[0].toDouble()); else if (direction == "vertical") d->tileRepeatOffsetPercent.setY(tokens[0].toDouble()); } } } return true; } QRectF KoPatternBackground::patternRectFromFillSize(const QSizeF &size) { - Q_D(KoPatternBackground); QRectF rect; switch (d->repeat) { case Tiled: rect.setTopLeft(d->offsetFromRect(QRectF(QPointF(), size), d->targetSize())); rect.setSize(d->targetSize()); break; case Original: rect.setLeft(0.5 * (size.width() - d->targetSize().width())); rect.setTop(0.5 * (size.height() - d->targetSize().height())); rect.setSize(d->targetSize()); break; case Stretched: rect.setTopLeft(QPointF(0.0, 0.0)); rect.setSize(size); break; } return rect; } diff --git a/libs/flake/KoPatternBackground.h b/libs/flake/KoPatternBackground.h index b442843bd4..cec17a6a66 100644 --- a/libs/flake/KoPatternBackground.h +++ b/libs/flake/KoPatternBackground.h @@ -1,128 +1,130 @@ /* This file is part of the KDE project * Copyright (C) 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. */ #ifndef KOPATTERNBACKGROUND_H #define KOPATTERNBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" +#include + class KoImageCollection; class KoOdfLoadingContext; class KoPatternBackgroundPrivate; class KoImageData; class QTransform; class QImage; class QPointF; class QRectF; /// A pattern shape background class KRITAFLAKE_EXPORT KoPatternBackground : public KoShapeBackground { public: /// Pattern rendering style enum PatternRepeat { Original, Tiled, Stretched }; /// Pattern reference point enum ReferencePoint { TopLeft, Top, TopRight, Left, Center, Right, BottomLeft, Bottom, BottomRight }; /// Constructs a new pattern background utilizing the given image collection explicit KoPatternBackground(KoImageCollection *collection); ~KoPatternBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Sets the transform matrix void setTransform(const QTransform &matrix); /// Returns the transform matrix QTransform transform() const; /// Sets a new pattern void setPattern(const QImage &pattern); /// Sets a new pattern. imageData memory is deleted inside this class void setPattern(KoImageData *imageData); /// Returns the pattern QImage pattern() const; /// Sets the pattern repeatgfl void setRepeat(PatternRepeat repeat); /// Returns the pattern repeat PatternRepeat repeat() const; /// Returns the pattern reference point identifier ReferencePoint referencePoint() const; /// Sets the pattern reference point void setReferencePoint(ReferencePoint referencePoint); /// Returns reference point offset in percent of the pattern display size QPointF referencePointOffset() const; /// Sets the reference point offset in percent of the pattern display size void setReferencePointOffset(const QPointF &offset); /// Returns tile repeat offset in percent of the pattern display size QPointF tileRepeatOffset() const; /// Sets the tile repeat offset in percent of the pattern display size void setTileRepeatOffset(const QPointF &offset); /// Returns the pattern display size QSizeF patternDisplaySize() const; /// Sets pattern display size void setPatternDisplaySize(const QSizeF &size); /// Returns the original image size QSizeF patternOriginalSize() const; /// reimplemented from KoShapeBackground void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; /// reimplemented from KoShapeBackground void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; /// reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; /// Returns the bounding rect of the pattern image based on the given fill size QRectF patternRectFromFillSize(const QSizeF &size); private: - Q_DECLARE_PRIVATE(KoPatternBackground) - Q_DISABLE_COPY(KoPatternBackground) + class Private; + QSharedDataPointer d; }; #endif // KOPATTERNBACKGROUND_H diff --git a/libs/flake/KoPointerEvent.cpp b/libs/flake/KoPointerEvent.cpp index f59cefc543..8b3bd714bd 100644 --- a/libs/flake/KoPointerEvent.cpp +++ b/libs/flake/KoPointerEvent.cpp @@ -1,256 +1,268 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; 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 "KoPointerEvent.h" #include "KoInputDeviceHandlerEvent.h" #include #include #include #include class Q_DECL_HIDDEN KoPointerEvent::Private { public: Private() : tabletEvent(0) , mouseEvent(0) + , touchEvent(0) , deviceEvent(0) , tabletButton(Qt::NoButton) , globalPos(0, 0) , pos(0, 0) , posZ(0) , rotationX(0) , rotationY(0) , rotationZ(0) {} QTabletEvent *tabletEvent; QMouseEvent *mouseEvent; + QTouchEvent *touchEvent; KoInputDeviceHandlerEvent *deviceEvent; Qt::MouseButton tabletButton; QPoint globalPos, pos; int posZ; int rotationX, rotationY, rotationZ; }; KoPointerEvent::KoPointerEvent(QMouseEvent *ev, const QPointF &pnt) : point(pnt), m_event(ev), d(new Private()) { Q_ASSERT(m_event); d->mouseEvent = ev; } KoPointerEvent::KoPointerEvent(QTabletEvent *ev, const QPointF &pnt) : point(pnt), m_event(ev), d(new Private()) { Q_ASSERT(m_event); d->tabletEvent = ev; } +KoPointerEvent::KoPointerEvent(QTouchEvent* ev, const QPointF &pnt) + : point(pnt) + , m_event(ev) + , d(new Private) +{ + Q_ASSERT(m_event); + d->touchEvent = ev; + d->pos = ev->touchPoints().at(0).pos().toPoint(); +} + KoPointerEvent::KoPointerEvent(KoInputDeviceHandlerEvent * ev, int x, int y, int z, int rx, int ry, int rz) : m_event(ev) , d(new Private()) { Q_ASSERT(m_event); d->deviceEvent = ev; d->pos = QPoint(x, y); d->posZ = z; d->rotationX = rx; d->rotationY = ry; d->rotationZ = rz; } KoPointerEvent::KoPointerEvent(KoPointerEvent *event, const QPointF &point) : point(point) , touchPoints(event->touchPoints) , m_event(event->m_event) , d(new Private(*(event->d))) { Q_ASSERT(m_event); } KoPointerEvent::KoPointerEvent(const KoPointerEvent &rhs) : point(rhs.point) , touchPoints(rhs.touchPoints) , m_event(rhs.m_event) , d(new Private(*rhs.d)) { } KoPointerEvent::~KoPointerEvent() { delete d; } Qt::MouseButton KoPointerEvent::button() const { if (d->mouseEvent) return d->mouseEvent->button(); else if (d->tabletEvent) return d->tabletButton; else if (d->deviceEvent) return d->deviceEvent->button(); else return Qt::NoButton; } Qt::MouseButtons KoPointerEvent::buttons() const { if (d->mouseEvent) return d->mouseEvent->buttons(); else if (d->tabletEvent) return d->tabletButton; else if (d->deviceEvent) return d->deviceEvent->buttons(); return Qt::NoButton; } QPoint KoPointerEvent::globalPos() const { if (d->mouseEvent) return d->mouseEvent->globalPos(); else if (d->tabletEvent) return d->tabletEvent->globalPos(); else return d->globalPos; } QPoint KoPointerEvent::pos() const { if (d->mouseEvent) return d->mouseEvent->pos(); else if (d->tabletEvent) return d->tabletEvent->pos(); else return d->pos; } qreal KoPointerEvent::pressure() const { if (d->tabletEvent) return d->tabletEvent->pressure(); else return 1.0; } qreal KoPointerEvent::rotation() const { if (d->tabletEvent) return d->tabletEvent->rotation(); else return 0.0; } qreal KoPointerEvent::tangentialPressure() const { if (d->tabletEvent) return std::fmod((d->tabletEvent->tangentialPressure() - (-1.0)) / (1.0 - (-1.0)), 2.0); else return 0.0; } int KoPointerEvent::x() const { if (d->tabletEvent) return d->tabletEvent->x(); else if (d->mouseEvent) return d->mouseEvent->x(); else return pos().x(); } int KoPointerEvent::xTilt() const { if (d->tabletEvent) return d->tabletEvent->xTilt(); else return 0; } int KoPointerEvent::y() const { if (d->tabletEvent) return d->tabletEvent->y(); else if (d->mouseEvent) return d->mouseEvent->y(); else return pos().y(); } int KoPointerEvent::yTilt() const { if (d->tabletEvent) return d->tabletEvent->yTilt(); else return 0; } int KoPointerEvent::z() const { if (d->tabletEvent) return d->tabletEvent->z(); else if (d->deviceEvent) return d->posZ; else return 0; } int KoPointerEvent::rotationX() const { return d->rotationX; } int KoPointerEvent::rotationY() const { return d->rotationY; } int KoPointerEvent::rotationZ() const { return d->rotationZ; } bool KoPointerEvent::isTabletEvent() { return dynamic_cast(m_event) != 0; } void KoPointerEvent::setTabletButton(Qt::MouseButton button) { d->tabletButton = button; } Qt::KeyboardModifiers KoPointerEvent::modifiers() const { if (d->tabletEvent) return d->tabletEvent->modifiers(); else if (d->mouseEvent) return d->mouseEvent->modifiers(); else if (d->deviceEvent) return d->deviceEvent->modifiers(); else return Qt::NoModifier; } diff --git a/libs/flake/KoPointerEvent.h b/libs/flake/KoPointerEvent.h index 0dc796ab0d..2dbd3d47c5 100644 --- a/libs/flake/KoPointerEvent.h +++ b/libs/flake/KoPointerEvent.h @@ -1,223 +1,225 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2007 Thomas Zander Copyright (C) 2012 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPOINTEREVENT_H #define KOPOINTEREVENT_H #include class QTabletEvent; class QMouseEvent; class QWheelEvent; class KoInputDeviceHandlerEvent; #include "kritaflake_export.h" struct KoTouchPoint { QTouchEvent::TouchPoint touchPoint; // the point in document coordinates QPointF lastPoint; QPointF point; }; /** * KoPointerEvent is a synthetic event that can be built from a mouse, * touch or tablet event. In addition to always providing tools with tablet * pressure characteristics, KoPointerEvent has both the original * (canvas based) position as well as the normalized position, that is, * the position of the event _in_ the document coordinates. */ class KRITAFLAKE_EXPORT KoPointerEvent { public: /** * Constructor. * * @param event the mouse event that is the base of this event. * @param point the zoomed point in the normal coordinate system. */ KoPointerEvent(QMouseEvent *event, const QPointF &point); /** * Constructor. * * @param event the tablet event that is the base of this event. * @param point the zoomed point in the normal coordinate system. */ KoPointerEvent(QTabletEvent *event, const QPointF &point); + KoPointerEvent(QTouchEvent* ev, const QPointF& pnt); + KoPointerEvent(KoInputDeviceHandlerEvent *event, int x, int y, int z = 0, int rx = 0, int ry = 0, int rz = 0); KoPointerEvent(KoPointerEvent *event, const QPointF& point); KoPointerEvent(const KoPointerEvent &rhs); ~KoPointerEvent(); /** * For classes that are handed this event, you can choose to accept (default) this event. * Acceptance signifies that you have handled this event and found it useful, the effect * of that will be that the event will not be handled to other event handlers. */ inline void accept() { m_event->accept(); } /** * For classes that are handed this event, you can choose to ignore this event. * Ignoring this event means you have not handled it and want to allow other event * handlers to try to handle it. */ inline void ignore() { m_event->ignore(); } /** * Returns the keyboard modifier flags that existed immediately before the event occurred. * See also QApplication::keyboardModifiers(). */ Qt::KeyboardModifiers modifiers() const; /// return if the event has been accepted. inline bool isAccepted() const { return m_event->isAccepted(); } /// return if this event was spontaneous (see QMouseEvent::spontaneous()) inline bool spontaneous() const { return m_event->spontaneous(); } /// return button pressed (see QMouseEvent::button()); Qt::MouseButton button() const; /// return buttons pressed (see QMouseEvent::buttons()); Qt::MouseButtons buttons() const; /// Return the position screen coordinates QPoint globalPos() const; /// return the position in widget coordinates QPoint pos() const; /** * return the pressure (or a default value). The range is 0.0 - 1.0 * and the default pressure (this is the pressure that will be given * when you use something like the mouse) is 1.0 */ qreal pressure() const; /// return the rotation (or a default value) qreal rotation() const; /** * return the tangential pressure (or a default value) * This is typically given by a finger wheel on an airbrush tool. The range * is from -1.0 to 1.0. 0.0 indicates a neutral position. Current airbrushes can * only move in the positive direction from the neutral position. If the device * does not support tangential pressure, this value is always 0.0. */ qreal tangentialPressure() const; /** * Return the x position in widget coordinates. * @see point */ int x() const; /** * Returns the angle between the device (a pen, for example) and the * perpendicular in the direction of the x axis. Positive values are * towards the tablet's physical right. The angle is in the range -60 * to +60 degrees. The default value is 0. */ int xTilt() const; /** * Return the y position in widget coordinates. * @see point */ int y() const; /** * Returns the angle between the device (a pen, for example) and the * perpendicular in the direction of the x axis. Positive values are * towards the tablet's physical right. The angle is in the range -60 * to +60 degrees. The default value is 0. */ int yTilt() const; /** * Returns the z position of the device. Typically this is represented * by a wheel on a 4D Mouse. If the device does not support a Z-axis, * this value is always zero. This is not the same as pressure. */ int z() const; /** * Returns the rotation around the X-axis. If the device does not support * this, the value is always zero. */ int rotationX() const; /** * Returns the rotation around the X-axis. If the device does not support * this, the value is always zero. */ int rotationY() const; /** * Returns the rotation around the Z-axis. If the device does not support * this, the value is always zero. */ int rotationZ() const; /// The point in document coordinates. const QPointF point; const QList touchPoints; /** * Returns if the event comes from a tablet */ bool isTabletEvent(); protected: friend class KoToolProxy; friend class KisToolProxy; friend class KisScratchPadEventFilter; /// called by KoToolProxy to set which button was pressed. void setTabletButton(Qt::MouseButton button); private: KoPointerEvent& operator=(const KoPointerEvent &rhs); // for the d-pointer police; we want to make accessors to the event inline, so this one stays here. QEvent *m_event; class Private; Private * const d; }; #endif diff --git a/libs/flake/KoResourceManager_p.cpp b/libs/flake/KoResourceManager_p.cpp index 680f94cd32..c16d758599 100644 --- a/libs/flake/KoResourceManager_p.cpp +++ b/libs/flake/KoResourceManager_p.cpp @@ -1,242 +1,245 @@ /* Copyright (c) 2006, 2011 Boudewijn Rempt (boud@valdyas.org) Copyright (C) 2007, 2010 Thomas Zander Copyright (c) 2008 Carlos Licea Copyright (c) 2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoResourceManager_p.h" #include #include #include "KoShape.h" #include "kis_assert.h" #include "kis_debug.h" void KoResourceManager::slotResourceInternalsChanged(int key) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_resources.contains(key)); notifyDerivedResourcesChanged(key, m_resources[key]); } void KoResourceManager::setResource(int key, const QVariant &value) { KoDerivedResourceConverterSP converter = m_derivedResources.value(key, KoDerivedResourceConverterSP()); if (converter) { const int sourceKey = converter->sourceKey(); const QVariant oldSourceValue = m_resources.value(sourceKey, QVariant()); bool valueChanged = false; const QVariant newSourceValue = converter->writeToSource(value, oldSourceValue, &valueChanged); if (valueChanged) { notifyResourceChanged(key, value); } if (oldSourceValue != newSourceValue) { m_resources[sourceKey] = newSourceValue; notifyResourceChanged(sourceKey, newSourceValue); } - - } else if (m_resources.contains(key)) { + } else if (m_resources.contains(key)) { const QVariant oldValue = m_resources.value(key, QVariant()); m_resources[key] = value; if (m_updateMediators.contains(key)) { m_updateMediators[key]->connectResource(value); } + if (oldValue != value) { notifyResourceChanged(key, value); } - } - else { + } else { m_resources[key] = value; + if (m_updateMediators.contains(key)) { + m_updateMediators[key]->connectResource(value); + } notifyResourceChanged(key, value); } + } void KoResourceManager::notifyResourceChanged(int key, const QVariant &value) { emit resourceChanged(key, value); notifyDerivedResourcesChanged(key, value); } void KoResourceManager::notifyDerivedResourcesChanged(int key, const QVariant &value) { QMultiHash::const_iterator it = m_derivedFromSource.constFind(key); QMultiHash::const_iterator end = m_derivedFromSource.constEnd(); while (it != end && it.key() == key) { KoDerivedResourceConverterSP converter = it.value(); if (converter->notifySourceChanged(value)) { notifyResourceChanged(converter->key(), converter->readFromSource(value)); } it++; } } QVariant KoResourceManager::resource(int key) const { KoDerivedResourceConverterSP converter = m_derivedResources.value(key, KoDerivedResourceConverterSP()); const int realKey = converter ? converter->sourceKey() : key; QVariant value = m_resources.value(realKey, QVariant()); return converter ? converter->readFromSource(value) : value; } void KoResourceManager::setResource(int key, const KoColor &color) { QVariant v; v.setValue(color); setResource(key, v); } void KoResourceManager::setResource(int key, KoShape *shape) { QVariant v; v.setValue(shape); setResource(key, v); } void KoResourceManager::setResource(int key, const KoUnit &unit) { QVariant v; v.setValue(unit); setResource(key, v); } KoColor KoResourceManager::koColorResource(int key) const { if (! m_resources.contains(key)) { KoColor empty; return empty; } return resource(key).value(); } KoShape *KoResourceManager::koShapeResource(int key) const { if (! m_resources.contains(key)) return 0; return resource(key).value(); } KoUnit KoResourceManager::unitResource(int key) const { return resource(key).value(); } bool KoResourceManager::boolResource(int key) const { if (! m_resources.contains(key)) return false; return m_resources[key].toBool(); } int KoResourceManager::intResource(int key) const { if (! m_resources.contains(key)) return 0; return m_resources[key].toInt(); } QString KoResourceManager::stringResource(int key) const { if (! m_resources.contains(key)) { QString empty; return empty; } return qvariant_cast(resource(key)); } QSizeF KoResourceManager::sizeResource(int key) const { if (! m_resources.contains(key)) { QSizeF empty; return empty; } return qvariant_cast(resource(key)); } bool KoResourceManager::hasResource(int key) const { KoDerivedResourceConverterSP converter = m_derivedResources.value(key, KoDerivedResourceConverterSP()); const int realKey = converter ? converter->sourceKey() : key; return m_resources.contains(realKey); } void KoResourceManager::clearResource(int key) { // we cannot remove a derived resource if (m_derivedResources.contains(key)) return; if (m_resources.contains(key)) { m_resources.remove(key); notifyResourceChanged(key, QVariant()); } } void KoResourceManager::addDerivedResourceConverter(KoDerivedResourceConverterSP converter) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_derivedResources.contains(converter->key())); m_derivedResources.insert(converter->key(), converter); m_derivedFromSource.insertMulti(converter->sourceKey(), converter); } bool KoResourceManager::hasDerivedResourceConverter(int key) { return m_derivedResources.contains(key); } void KoResourceManager::removeDerivedResourceConverter(int key) { Q_ASSERT(m_derivedResources.contains(key)); KoDerivedResourceConverterSP converter = m_derivedResources.value(key); m_derivedResources.remove(key); m_derivedFromSource.remove(converter->sourceKey(), converter); } void KoResourceManager::addResourceUpdateMediator(KoResourceUpdateMediatorSP mediator) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_updateMediators.contains(mediator->key())); m_updateMediators.insert(mediator->key(), mediator); connect(mediator.data(), SIGNAL(sigResourceChanged(int)), SLOT(slotResourceInternalsChanged(int))); } bool KoResourceManager::hasResourceUpdateMediator(int key) { return m_updateMediators.contains(key); } void KoResourceManager::removeResourceUpdateMediator(int key) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_updateMediators.contains(key)); m_updateMediators.remove(key); } diff --git a/libs/flake/KoSelection.cpp b/libs/flake/KoSelection.cpp index 94f96360dd..81c2910809 100644 --- a/libs/flake/KoSelection.cpp +++ b/libs/flake/KoSelection.cpp @@ -1,263 +1,260 @@ /* This file is part of the KDE project Copyright (C) 2006 Boudewijn Rempt Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006-2007,2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSelection.h" #include "KoSelection_p.h" #include "KoShapeContainer.h" #include "KoShapeGroup.h" #include "KoPointerEvent.h" #include "KoShapePaintingContext.h" #include "kis_algebra_2d.h" #include "krita_container_utils.h" #include #include "kis_debug.h" KoSelection::KoSelection(QObject *parent) - : QObject(parent), - KoShape(new KoSelectionPrivate(this)) + : QObject(parent) + , KoShape() + , d(new Private) { - Q_D(KoSelection); connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged())); } +KoSelection::KoSelection(const KoSelection &rhs) + : QObject() + , KoShape(rhs) + , d(rhs.d) +{ +} + KoSelection::~KoSelection() { } void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintcontext); } void KoSelection::setSize(const QSizeF &size) { Q_UNUSED(size); qWarning() << "WARNING: KoSelection::setSize() should never be used!"; } QSizeF KoSelection::size() const { return outlineRect().size(); } QRectF KoSelection::outlineRect() const { QPolygonF globalPolygon; Q_FOREACH (KoShape *shape, selectedVisibleShapes()) { globalPolygon = globalPolygon.united( shape->absoluteTransformation(0).map(QPolygonF(shape->outlineRect()))); } const QPolygonF localPolygon = transformation().inverted().map(globalPolygon); return localPolygon.boundingRect(); } QRectF KoSelection::boundingRect() const { return KoShape::boundingRect(selectedVisibleShapes()); } void KoSelection::select(KoShape *shape) { - Q_D(KoSelection); KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); if (!shape->isSelectable() || !shape->isVisible()) { return; } // check recursively if (isSelected(shape)) { return; } // find the topmost parent to select while (KoShapeGroup *parentGroup = dynamic_cast(shape->parent())) { shape = parentGroup; } d->selectedShapes << shape; shape->addShapeChangeListener(this); if (d->selectedShapes.size() == 1) { setTransformation(shape->absoluteTransformation(0)); } else { setTransformation(QTransform()); } d->selectionChangedCompressor.start(); } void KoSelection::deselect(KoShape *shape) { - Q_D(KoSelection); if (!d->selectedShapes.contains(shape)) return; d->selectedShapes.removeAll(shape); shape->removeShapeChangeListener(this); if (d->selectedShapes.size() == 1) { setTransformation(d->selectedShapes.first()->absoluteTransformation(0)); } d->selectionChangedCompressor.start(); } void KoSelection::deselectAll() { - Q_D(KoSelection); if (d->selectedShapes.isEmpty()) return; Q_FOREACH (KoShape *shape, d->selectedShapes) { shape->removeShapeChangeListener(this); } // reset the transformation matrix of the selection setTransformation(QTransform()); d->selectedShapes.clear(); d->selectionChangedCompressor.start(); } int KoSelection::count() const { - Q_D(const KoSelection); return d->selectedShapes.size(); } bool KoSelection::hitTest(const QPointF &position) const { - Q_D(const KoSelection); Q_FOREACH (KoShape *shape, d->selectedShapes) { if (shape->isVisible()) continue; if (shape->hitTest(position)) return true; } return false; } const QList KoSelection::selectedShapes() const { - Q_D(const KoSelection); return d->selectedShapes; } const QList KoSelection::selectedVisibleShapes() const { QList shapes = selectedShapes(); KritaUtils::filterContainer (shapes, [](KoShape *shape) { return shape->isVisible(); }); return shapes; } const QList KoSelection::selectedEditableShapes() const { QList shapes = selectedShapes(); KritaUtils::filterContainer (shapes, [](KoShape *shape) { return shape->isShapeEditable(); }); return shapes; } const QList KoSelection::selectedEditableShapesAndDelegates() const { QList shapes; Q_FOREACH (KoShape *shape, selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes.append(shape); } else { Q_FOREACH (KoShape *delegatedShape, delegates) { shapes.append(delegatedShape); } } } return shapes; } bool KoSelection::isSelected(const KoShape *shape) const { - Q_D(const KoSelection); if (shape == this) return true; const KoShape *tmpShape = shape; while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()) { tmpShape = tmpShape->parent(); } return tmpShape; } KoShape *KoSelection::firstSelectedShape() const { - Q_D(const KoSelection); return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0; } void KoSelection::setActiveLayer(KoShapeLayer *layer) { - Q_D(KoSelection); d->activeLayer = layer; emit currentLayerChanged(layer); } KoShapeLayer* KoSelection::activeLayer() const { - Q_D(const KoSelection); return d->activeLayer; } void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(shape); if (type == KoShape::Deleted) { deselect(shape); // HACK ALERT: the caller will also remove the listener, which was // removed in deselect(), so re-add it here shape->addShapeChangeListener(this); } } void KoSelection::saveOdf(KoShapeSavingContext &) const { } bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &) { return true; } diff --git a/libs/flake/KoSelection.h b/libs/flake/KoSelection.h index b1122477e3..b11578304d 100644 --- a/libs/flake/KoSelection.h +++ b/libs/flake/KoSelection.h @@ -1,165 +1,170 @@ /* This file is part of the KDE project Copyright (C) 2006 Boudewijn Rempt Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007,2009 Thomas Zander Copyright (C) 2006,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. */ #ifndef KOSELECTION_H #define KOSELECTION_H #include #include "KoShape.h" #include "KoFlake.h" #include "kritaflake_export.h" class KoViewConverter; class KoShapeLayer; class KoSelectionPrivate; /** * A selection is a shape that contains a number of references * to shapes. That means that a selection can be manipulated in * the same way as a single shape. * * Note that a single shape can be selected in one view, and not in * another, and that in a single view, more than one selection can be * present. So selections should not be seen as singletons, or as * something completely transient. * * A selection, however, should not be selectable. We need to think * a little about the interaction here. */ class KRITAFLAKE_EXPORT KoSelection : public QObject, public KoShape, public KoShape::ShapeChangeListener { Q_OBJECT public: KoSelection(QObject *parent = 0); ~KoSelection() override; void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; void setSize(const QSizeF &size) override; QSizeF size() const override; QRectF outlineRect() const override; QRectF boundingRect() const override; /** * Adds a shape to the selection. * * If the shape is a KoShapeGroup all of its child shapes are automatically added * to the selection. * If the shape has no parent or is not a KoShapeGroup, only the given shape is * added to the selection. * If the given shape is a child of a KoShapeGroup and recursive selection is enabled * the all parents and their child shapes up to the toplevel KoShapeGroup are added to * the selection. * * @param shape the shape to add to the selection */ void select(KoShape *shape); /** * Removes a selected shape. * * If the shape is a KoShapeGroup all of its child shapes are automatically removed * from the selection. * If the shape has no parent or is not a KoShapeGroup, only the given shape is * removed from the selection. * If the given shape is a child of a KoShapeGroup and recursive selection is enabled * the all parents and their child shape up to the toplevel KoShapeGroup are removed * from the selection. * * @param shape the shape to remove from the selection */ void deselect(KoShape *shape); /// clear the selections list void deselectAll(); /** * Return the list of selected shapes * @return the list of selected shapes */ const QList selectedShapes() const; /** * Same as selectedShapes() but only for shapes in visible state. Used by * the algorithms that draw shapes on the image */ const QList selectedVisibleShapes() const; /** * Same as selectedShapes() but only for editable shapes. Used by * the algorithms that modify the image */ const QList selectedEditableShapes() const; /** * Same as selectedEditableShapes() but also includes shapes delegates. * Used for */ const QList selectedEditableShapesAndDelegates() const; /** * Return the first selected shape, or 0 if there is nothing selected. */ KoShape *firstSelectedShape() const; /// return true if the shape is selected bool isSelected(const KoShape *shape) const; /// return the selection count, i.e. the number of all selected shapes int count() const; bool hitTest(const QPointF &position) const override; /** * Sets the currently active layer. * @param layer the new active layer */ void setActiveLayer(KoShapeLayer *layer); /** * Returns a currently active layer. * * @return the currently active layer, or zero if there is none */ KoShapeLayer *activeLayer() const; void notifyShapeChanged(ChangeType type, KoShape *shape) override; Q_SIGNALS: /// emitted when the selection is changed void selectionChanged(); /// emitted when the current layer is changed void currentLayerChanged(const KoShapeLayer *layer); private: void saveOdf(KoShapeSavingContext &) const override; bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override; - Q_DECLARE_PRIVATE_D(KoShape::d_ptr, KoSelection) +protected: + KoSelection(const KoSelection &rhs); + +private: + class Private; + QSharedDataPointer d; }; #endif diff --git a/libs/flake/KoSelection_p.h b/libs/flake/KoSelection_p.h index 136b75d5a2..5fa1ce5579 100644 --- a/libs/flake/KoSelection_p.h +++ b/libs/flake/KoSelection_p.h @@ -1,44 +1,48 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSELECTIONPRIVATE_H #define KOSELECTIONPRIVATE_H -#include "KoShape_p.h" +#include #include "kis_thread_safe_signal_compressor.h" class KoShapeGroup; -class KoSelectionPrivate : public KoShapePrivate +class KoSelection::Private : public QSharedData { public: - explicit KoSelectionPrivate(KoSelection *parent) - : KoShapePrivate(parent), - activeLayer(0), - selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE) + explicit Private() + : QSharedData() + , activeLayer(0) + , selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE) {} + explicit Private(const Private &) + : QSharedData() + , activeLayer(0) + , selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE) + { + } QList selectedShapes; KoShapeLayer *activeLayer; KisThreadSafeSignalCompressor selectionChangedCompressor; - - Q_DECLARE_PUBLIC(KoSelection) }; #endif diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 89a4acb18a..9a88c382c3 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2541 +1,2426 @@ /* 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 #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 "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) +// KoShape::Private + +KoShape::Private::Private() + : QSharedData() + , 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); - - /** - * The shape must have already been detached from all the parents and - * shape managers. Otherwise we migh accidentally request some RTTI - * information, which is not available anymore (we are in d-tor). - * - * TL;DR: fix the code that caused this destruction without unparenting - * instead of trying to remove these assert! - */ - KIS_SAFE_ASSERT_RECOVER (!parent) { - parent->removeShape(q); - } - - KIS_SAFE_ASSERT_RECOVER (shapeManagers.isEmpty()) { - Q_FOREACH (KoShapeManager *manager, shapeManagers) { - manager->shapeInterface()->notifyShapeDestructed(q); - } - shapeManagers.clear(); - } - +KoShape::Private::Private(const Private &rhs) + : QSharedData() + , 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) +{ +} + +KoShape::Private::~Private() +{ if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } -void KoShapePrivate::shapeChanged(KoShape::ChangeType type) +void KoShape::shapeChangedPriv(KoShape::ChangeType type) { - Q_Q(KoShape); - if (parent) - parent->model()->childChanged(q, type); + if (d->parent) + d->parent->model()->childChanged(this, type); - q->shapeChanged(type); + this->shapeChanged(type); - Q_FOREACH (KoShape * shape, dependees) { - shape->shapeChanged(type, q); + Q_FOREACH (KoShape * shape, d->dependees) { + shape->shapeChanged(type, this); } - Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { - listener->notifyShapeChangedImpl(type, q); + Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { + listener->notifyShapeChangedImpl(type, this); } } -void KoShapePrivate::addShapeManager(KoShapeManager *manager) +void KoShape::addShapeManager(KoShapeManager *manager) { - shapeManagers.insert(manager); + d->shapeManagers.insert(manager); } -void KoShapePrivate::removeShapeManager(KoShapeManager *manager) +void KoShape::removeShapeManager(KoShapeManager *manager) { - shapeManagers.remove(manager); + d->shapeManagers.remove(manager); } -void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const +void KoShape::Private::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(); break; case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); break; 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 +void KoShape::Private::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(); break; case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); break; 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) +QString KoShape::Private::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 const qint16 KoShape::maxZIndex = std::numeric_limits::max(); const qint16 KoShape::minZIndex = std::numeric_limits::min(); KoShape::KoShape() - : d_ptr(new KoShapePrivate(this)) + : d(new Private) { notifyChanged(); } -KoShape::KoShape(KoShapePrivate *dd) - : d_ptr(dd) +KoShape::KoShape(const KoShape &rhs) + : d(rhs.d) { } KoShape::~KoShape() { - Q_D(KoShape); - d->shapeChanged(Deleted); + shapeChangedPriv(Deleted); d->listeners.clear(); - delete d_ptr; + /** + * The shape must have already been detached from all the parents and + * shape managers. Otherwise we migh accidentally request some RTTI + * information, which is not available anymore (we are in d-tor). + * + * TL;DR: fix the code that caused this destruction without unparenting + * instead of trying to remove these assert! + */ + KIS_SAFE_ASSERT_RECOVER (!d->parent) { + d->parent->removeShape(this); + } + + KIS_SAFE_ASSERT_RECOVER (d->shapeManagers.isEmpty()) { + Q_FOREACH (KoShapeManager *manager, d->shapeManagers) { + manager->shapeInterface()->notifyShapeDestructed(this); + } + d->shapeManagers.clear(); + } } 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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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; + setSizeImpl(newSize); if (oldSize == newSize) return; notifyChanged(); - d->shapeChanged(SizeChanged); + shapeChangedPriv(SizeChanged); +} + +void KoShape::setSizeImpl(const QSizeF &size) const +{ + d->size = size; } 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); + shapeChangedPriv(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 = outlineRect(); 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); + shapeChangedPriv(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { - Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); - d->shapeChanged(GenericMatrixChange); + shapeChangedPriv(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 * coincide, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ /** * The algorithm below doesn't correctly handle the case when the two pointers actually * point to the same shape. So just check it in advance to guarantee strict weak ordering * relation requirement */ if (s1 == s2) return false; // 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); + shapeChangedPriv(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; } bool KoShape::hasCommonParent(const KoShape *shape) const { const KoShape *thisShape = this; while (thisShape) { const KoShape *otherShape = shape; while (otherShape) { if (thisShape == otherShape) { return true; } otherShape = otherShape->parent(); } thisShape = thisShape->parent(); } return false; } qint16 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 { 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); + shapeChangedPriv(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(false); // 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; + d->localMatrix = shape->d->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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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(qint16 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 (!d->visible) return false; KoShapeContainer * parentShape = parent(); if (parentShape) { return parentShape->isVisible(true); } 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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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); + shapeChangedPriv(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::isShapeEditable(bool recursive) const { - Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (recursive && d->parent) { return d->parent->isShapeEditable(true); } 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); + QString fill = KoShape::Private::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); + QString styleName = KoShape::Private::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); + QString stroke = KoShape::Private::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 +KoShapeShadow *KoShape::Private::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); + QString shadowStyle = KoShape::Private::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 +KoBorder *KoShape::Private::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().addAttribute("svg:width", s.width()); context.xmlWriter().addAttribute("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().addAttribute("svg:x", p.x()); context.xmlWriter().addAttribute("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().addAttribute("svg:x", matrix.dx()); context.xmlWriter().addAttribute("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().addAttribute("svg:x", cp.value().position.x()); context.xmlWriter().addAttribute("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: 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: 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 whereas // 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::listeners() const +{ + return d->listeners; +} + 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/KoShape.h b/libs/flake/KoShape.h index 50fb9ee339..50fca444dd 100644 --- a/libs/flake/KoShape.h +++ b/libs/flake/KoShape.h @@ -1,1305 +1,1313 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006, 2008 C. Boemann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2007-2009,2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPE_H #define KOSHAPE_H #include "KoFlake.h" #include "KoFlakeTypes.h" #include "KoConnectionPoint.h" #include #include #include +#include #include #include "kritaflake_export.h" class QPainter; class QRectF; class QPainterPath; class QTransform; class KoShapeContainer; class KoShapeStrokeModel; class KoShapeUserData; class KoViewConverter; class KoShapeApplicationData; class KoShapeSavingContext; class KoShapeLoadingContext; class KoGenStyle; class KoShapeShadow; -class KoShapePrivate; class KoFilterEffectStack; class KoSnapData; class KoClipPath; class KoClipMask; class KoShapePaintingContext; class KoShapeAnchor; class KoBorder; struct KoInsets; class KoShapeBackground; class KisHandlePainterHelper; +class KoShapeManager; /** * Base class for all flake shapes. Shapes extend this class * to allow themselves to be manipulated. This class just represents * a graphical shape in the document and can be manipulated by some default * tools in this library. * * Due to the limited responsibility of this class, the extending object * can have any data backend and is responsible for painting itself. * * We strongly suggest that any extending class will use a Model View * Controller (MVC) design where the View part is all in this class, as well * as the one that inherits from this one. This allows the data that rests * in the model to be reused in different parts of the document. For example * by having two flake objects that show that same data. Or each showing a section of it. * * The KoShape data is completely in postscript-points (pt) (see KoUnit * for conversion methods to and from points). * This image will explain the real-world use of the shape and its options. *
* The Rotation center can be returned with absolutePosition() * *

Flake objects can be created in three ways: *

    *
  • a simple new KoDerivedFlake(), *
  • through an associated tool, *
  • through a factory *
* *

Shape interaction notifications

* We had several notification methods that allow your shape to be notified of changes in other * shapes positions or rotation etc. *
  1. The most general is KoShape::shapeChanged().
    * a virtual method that you can use to check various changed to your shape made by tools or otherwise.
  2. *
  3. for shape hierarchies the parent may receive a notification when a child was modified. * This is done though KoShapeContainerModel::childChanged()
  4. *
  5. any shape that is at a similar position as another shape there is collision detection. * You can register your shape to be sensitive to any changes like moving or whatever to * other shapes that intersect yours. * Such changes will then be notified to your shape using the method from (1) You should call * KoShape::setCollisionDetection(bool) to enable this. *
*/ class KRITAFLAKE_EXPORT KoShape { public: /// Used by shapeChanged() to select which change was made enum ChangeType { PositionChanged, ///< used after a setPosition() RotationChanged, ///< used after a setRotation() ScaleChanged, ///< used after a scale() ShearChanged, ///< used after a shear() SizeChanged, ///< used after a setSize() GenericMatrixChange, ///< used after the matrix was changed without knowing which property explicitly changed KeepAspectRatioChange, ///< used after setKeepAspectRatio() ParentChanged, ///< used after a setParent() CollisionDetected, ///< used when another shape moved in our boundingrect Deleted, ///< the shape was deleted StrokeChanged, ///< the shapes stroke has changed BackgroundChanged, ///< the shapes background has changed ShadowChanged, ///< the shapes shadow has changed BorderChanged, ///< the shapes border has changed ParameterChanged, ///< the shapes parameter has changed (KoParameterShape only) ContentChanged, ///< the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape TextRunAroundChanged, ///< used after a setTextRunAroundSide() ChildChanged, ///< a child of a container was changed/removed. This is propagated to all parents ConnectionPointChanged, ///< a connection point has changed ClipPathChanged, ///< the shapes clip path has changed TransparencyChanged ///< the shapetransparency value has changed }; /// The behavior text should do when intersecting this shape. enum TextRunAroundSide { BiggestRunAroundSide, ///< Run other text around the side that has the most space LeftRunAroundSide, ///< Run other text around the left side of the frame RightRunAroundSide, ///< Run other text around the right side of the frame EnoughRunAroundSide, ///< Run other text dynamically around both sides of the shape, provided there is sufficient space left BothRunAroundSide, ///< Run other text around both sides of the shape NoRunAround, ///< The text will be completely avoiding the frame by keeping the horizontal space that this frame occupies blank. RunThrough ///< The text will completely ignore the frame and layout as if it was not there }; /// The behavior text should do when intersecting this shape. enum TextRunAroundContour { ContourBox, /// Run other text around a bounding rect of the outline ContourFull, ///< Run other text around also on the inside ContourOutside ///< Run other text around only on the outside }; /** * TODO */ enum RunThroughLevel { Background, Foreground }; /** * @brief Constructor */ KoShape(); /** * @brief Destructor */ virtual ~KoShape(); /** * @brief creates a deep copy of the shape or shape's subtree * @return a cloned shape */ virtual KoShape* cloneShape() const; /** * @brief Paint the shape fill * The class extending this one is responsible for painting itself. Since we do not * assume the shape is square the paint must also clear its background if it will draw * something transparent on top. * This can be done with a method like: * painter.fillRect(converter.normalToView(QRectF(QPointF(0.0,0.0), size())), background()); * Or equivalent for non-square objects. * Do note that a shape's top-left is always at coordinate 0,0. Even if the shape itself is rotated * or translated. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() * @param paintcontext the painting context. */ virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0; /** * @brief paintStroke paints the shape's stroked outline * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() * @param paintcontext the painting context. */ virtual void paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext); /** * @brief Paint the shape's border * This is a helper function that could be called from the paint() method of all shapes. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() */ virtual void paintBorder(QPainter &painter, const KoViewConverter &converter); /** * Load a shape from odf * * @param context the KoShapeLoadingContext used for loading * @param element element which represents the shape in odf * * @return false if loading failed */ virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * @brief store the shape data as ODF XML. * This is the method that will be called when saving a shape as a described in * OpenDocument 9.2 Drawing Shapes. * @see saveOdfAttributes */ virtual void saveOdf(KoShapeSavingContext &context) const = 0; /** * This method can be used while saving the shape as ODF to add the data * stored on this shape to the current element. * * @param context the context for the current save. * @param attributes a number of OdfAttribute items to state which attributes to save. * @see saveOdf */ void saveOdfAttributes(KoShapeSavingContext &context, int attributes) const; /** * This method can be used while saving the shape as Odf to add common child elements * * The office:event-listeners and draw:glue-point are saved. * @param context the context for the current save. */ void saveOdfCommonChildElements(KoShapeSavingContext &context) const; /** * This method can be used to save contour data from the clipPath() * * The draw:contour-polygon or draw:contour-path elements are saved. * @param context the context for the current save. * @param originalSize the original size of the unscaled image. */ void saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const; /** * @brief Scale the shape using the zero-point which is the top-left corner. * @see position() * * @param sx scale in x direction * @param sy scale in y direction */ void scale(qreal sx, qreal sy); /** * @brief Rotate the shape (relative) * * The shape will be rotated from the current rotation using the center of the shape using the size() * * @param angle change the angle of rotation increasing it with 'angle' degrees */ void rotate(qreal angle); /** * Return the current rotation in degrees. * It returns NaN if the shape has a shearing or scaling transformation applied. */ qreal rotation() const; /** * @brief Shear the shape * The shape will be sheared using the zero-point which is the top-left corner. * @see position() * * @param sx shear in x direction * @param sy shear in y direction */ void shear(qreal sx, qreal sy); /** * @brief Resize the shape * * @param size the new size of the shape. This is different from scaling as * scaling is a so called secondary operation which is comparable to zooming in * instead of changing the size of the basic shape. * Easiest example of this difference is that using this method will not distort the * size of pattern-fills and strokes. */ virtual void setSize(const QSizeF &size); /** * @brief Get the size of the shape in pt. * * The size is in shape coordinates. * * @return the size of the shape as set by setSize() */ virtual QSizeF size() const; /** * @brief Set the position of the shape in pt * * @param position the new position of the shape */ virtual void setPosition(const QPointF &position); /** * @brief Get the position of the shape in pt * * @return the position of the shape */ QPointF position() const; /** * @brief Check if the shape is hit on position * @param position the position where the user clicked. * @return true when it hits. */ virtual bool hitTest(const QPointF &position) const; /** * @brief Get the bounding box of the shape * * This includes the line width and the shadow of the shape * * @return the bounding box of the shape */ virtual QRectF boundingRect() const; /** * Get the united bounding box of a group of shapes. This is a utility * function used in many places in Krita. */ static QRectF boundingRect(const QList &shapes); /** * @return the bounding rect of the outline of the shape measured * in absolute coordinate system. Please note that in contrast to * boundingRect() this rect doesn't include the stroke and other * insets. */ QRectF absoluteOutlineRect(KoViewConverter *converter = 0) const; /** * Same as a member function, but applies to a list of shapes and returns a * united rect. */ static QRectF absoluteOutlineRect(const QList &shapes, KoViewConverter *converter = 0); /** * @brief Add a connector point to the shape * * A connector is a place on the shape that allows a graphical connection to be made * using a line, for example. * * @param point the connection point to add * @return the id of the new connection point */ int addConnectionPoint(const KoConnectionPoint &point); /** * Sets data of connection point with specified id. * * The position of the connector is restricted to the bounding rectangle of the shape. * When setting a default connection point, the new position is ignored, as these * are fixed at their default position. * The function will insert a new connection point if the specified id was not used * before. * * @param connectionPointId the id of the connection point to set * @param point the connection point data * @return false if specified connection point id is invalid, else true */ bool setConnectionPoint(int connectionPointId, const KoConnectionPoint &point); /// Checks if a connection point with the specified id exists bool hasConnectionPoint(int connectionPointId) const; /// Returns connection point with specified connection point id KoConnectionPoint connectionPoint(int connectionPointId) const; /** * Return a list of the connection points that have been added to this shape. * All the points are relative to the shape position, see absolutePosition(). * @return a list of the connectors that have been added to this shape. */ KoConnectionPoints connectionPoints() const; /// Removes connection point with specified id void removeConnectionPoint(int connectionPointId); /// Removes all connection points void clearConnectionPoints(); /** * Return the side text should flow around this shape. This implements the ODF style:wrap * attribute that specifies how text is displayed around a frame or graphic object. */ TextRunAroundSide textRunAroundSide() const; /** * Set the side text should flow around this shape. * @param side the requested side * @param runThrough run through the foreground or background or... */ void setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrough = Background); /** * The space between this shape's left edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceLeft() const; /** * Set the space between this shape's left edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceLeft(qreal distance); /** * The space between this shape's top edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceTop() const; /** * Set the space between this shape's top edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceTop(qreal distance); /** * The space between this shape's right edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceRight() const; /** * Set the space between this shape's right edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceRight(qreal distance); /** * The space between this shape's bottom edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceBottom() const; /** * Set the space between this shape's bottom edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceBottom(qreal distance); /** * Return the threshold above which text should flow around this shape. * The text will not flow around the shape on a side unless the space available on that side * is above this threshold. Only used when the text run around side is EnoughRunAroundSide. * @return threshold the threshold */ qreal textRunAroundThreshold() const; /** * Set the threshold above which text should flow around this shape. * The text will not flow around the shape on a side unless the space available on that side * is above this threshold. Only used when the text run around side is EnoughRunAroundSide. * @param threshold the new threshold */ void setTextRunAroundThreshold(qreal threshold); /** * Return the how tight text run around is done around this shape. * @return the contour */ TextRunAroundContour textRunAroundContour() const; /** * Set how tight text run around is done around this shape. * @param contour the new contour */ void setTextRunAroundContour(TextRunAroundContour contour); /** * Set the KoShapeAnchor */ void setAnchor(KoShapeAnchor *anchor); /** * Return the KoShapeAnchor, or 0 */ KoShapeAnchor *anchor() const; /** * Set the minimum height of the shape. * Currently it's not respected but only for informational purpose * @param height the minimum height of the frame. */ void setMinimumHeight(qreal height); /** * Return the minimum height of the shape. * @return the minimum height of the shape. Default is 0.0. */ qreal minimumHeight() const; /** * Set the background of the shape. * A shape background can be a plain color, a gradient, a pattern, be fully transparent * or have a complex fill. * Setting such a background will allow the shape to be filled and will be able to tell * if it is transparent or not. * * If the shape inherited the background from its parent, its stops inheriting it, that * is inheritBackground property resets to false. * * @param background the new shape background. */ void setBackground(QSharedPointer background); /** * return the brush used to paint te background of this shape with. * A QBrush can have a plain color, be fully transparent or have a complex fill. * setting such a brush will allow the shape to fill itself using that brush and * will be able to tell if its transparent or not. * @return the background-brush */ QSharedPointer background() const; /** * @brief setInheritBackground marks a shape as inhiriting the background * from the parent shape. NOTE: The currently selected background is destroyed. * @param value true if the shape should inherit the filling background */ void setInheritBackground(bool value); /** * @brief inheritBackground shows if the shape inherits background from its parent * @return true if the shape inherits the fill */ bool inheritBackground() const; /** * Returns true if there is some transparency, false if the shape is fully opaque. * The default implementation will just return if the background has some transparency, * you should override it and always return true if your shape is not square. * @return if the shape is (partly) transparent. */ virtual bool hasTransparency() const; /** * Sets shape level transparency. * @param transparency the new shape level transparency */ void setTransparency(qreal transparency); /** * Returns the shape level transparency. * @param recursive when true takes the parents transparency into account */ qreal transparency(bool recursive=false) const; /** * Retrieve the z-coordinate of this shape. * The zIndex property is used to determine which shape lies on top of other objects. * An shape with a higher z-order is on top, and can obscure another shape. * @return the z-index of this shape. * @see setZIndex() */ qint16 zIndex() const; /** * Set the z-coordinate of this shape. * The zIndex property is used to determine which shape lies on top of other objects. * An shape with a higher z-order is on top, and can obscure, another shape. *

Just like two objects having the same x or y coordinate will make them 'touch', * so will two objects with the same z-index touch on the z plane. In layering the * shape this, however, can cause a little confusion as one always has to be on top. * The layering if two overlapping objects have the same index is implementation dependent * and probably depends on the order in which they are added to the shape manager. * @param zIndex the new z-index; */ void setZIndex(qint16 zIndex); /** * Maximum value of z-index */ static const qint16 maxZIndex; /** * Minimum value of z-index */ static const qint16 minZIndex; /** * Retrieve the run through property of this shape. * The run through property is used to determine if the shape is behind, inside or before text. * @return the run through of this shape. */ int runThrough(); /** * Set the run through property of this shape. * The run through property is used to determine if the shape is behind, inside or before text. * @param runThrough the new run through; */ virtual void setRunThrough(short int runThrough); /** * Changes the Shape to be visible or invisible. * Being visible means being painted, as well as being used for * things like guidelines or searches. * @param on when true; set the shape to be visible. * @see setGeometryProtected(), setContentProtected(), setSelectable() */ void setVisible(bool on); /** * Returns current visibility state of this shape. * Being visible means being painted, as well as being used for * things like guidelines or searches. * @param recursive when true, checks visibility recursively * @return current visibility state of this shape. * @see isGeometryProtected(), isContentProtected(), isSelectable() */ bool isVisible(bool recursive = true) const; /** * Changes the shape to be printable or not. The default is true. * * If a Shape's print flag is true, the shape will be printed. If * false, the shape will not be printed. If a shape is not visible (@see isVisible), * it isPrinted will return false, too. */ void setPrintable(bool on); /** * Returns the current printable state of this shape. * * A shape can be visible but not printable, not printable and not visible * or visible and printable, but not invisible and still printable. * * @return current printable state of this shape. */ bool isPrintable() const; /** * Makes it possible for the user to select this shape. * This parameter defaults to true. * @param selectable when true; set the shape to be selectable by the user. * @see setGeometryProtected(), setContentProtected(), setVisible() */ void setSelectable(bool selectable); /** * Returns if this shape can be selected by the user. * @return true only when the object is selectable. * @see isGeometryProtected(), isContentProtected(), isVisible() */ bool isSelectable() const; /** * Tells the shape to have its position/rotation and size protected from user-changes. * The geometry being protected means the user can not change shape or position of the * shape. This includes any matrix operation such as rotation. * @param on when true; set the shape to have its geometry protected. * @see setContentProtected(), setSelectable(), setVisible() */ void setGeometryProtected(bool on); /** * Returns current geometry protection state of this shape. * The geometry being protected means the user can not change shape or position of the * shape. This includes any matrix operation such as rotation. * @return current geometry protection state of this shape. * @see isContentProtected(), isSelectable(), isVisible() */ bool isGeometryProtected() const; /** * Marks the shape to have its content protected against editing. * Content protection is a hint for tools to disallow the user editing the content. * @param protect when true set the shapes content to be protected from user modification. * @see setGeometryProtected(), setSelectable(), setVisible() */ void setContentProtected(bool protect); /** * Returns current content protection state of this shape. * Content protection is a hint for tools to disallow the user editing the content. * @return current content protection state of this shape. * @see isGeometryProtected(), isSelectable(), isVisible() */ bool isContentProtected() const; /** * Returns the parent, or 0 if there is no parent. * @return the parent, or 0 if there is no parent. */ KoShapeContainer *parent() const; /** * Set the parent of this shape. * @param parent the new parent of this shape. Can be 0 if the shape has no parent anymore. */ void setParent(KoShapeContainer *parent); /** * @brief inheritsTransformFromAny checks if the shape inherits transformation from * any of the shapes listed in \p ancestorsInQuestion. The inheritance is checked * in recursive way. * @return true if there is a (transitive) transformation-wise parent found in \p ancestorsInQuestion */ bool inheritsTransformFromAny(const QList ancestorsInQuestion) const; /** * @return true if this shape has a common parent with \p shape */ bool hasCommonParent(const KoShape *shape) const; /** * Request a repaint to be queued. * The repaint will be of the entire Shape, including its selection handles should this * shape be selected. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. */ virtual void update() const; /** * Request a repaint to be queued. * The repaint will be restricted to the parameters rectangle, which is expected to be * in absolute coordinates of the canvas and it is expected to be * normalized. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. * @param rect the rectangle (in pt) to queue for repaint. */ virtual void updateAbsolute(const QRectF &rect) const; /// Used by compareShapeZIndex() to order shapes enum ChildZOrderPolicy { ChildZDefault, ChildZParentChild = ChildZDefault, ///< normal parent/child ordering ChildZPassThrough ///< children are considered equal to this shape }; /** * Returns if during compareShapeZIndex() how this shape portrays the values * of its children. The default behaviour is to let this shape's z values take * the place of its childrens values, so you get a parent/child relationship. * The children are naturally still ordered relatively to their z values * * But for special cases (like Calligra's TextShape) it can be overloaded to return * ChildZPassThrough which means the children keep their own z values * @returns the z order policy of this shape */ virtual ChildZOrderPolicy childZOrderPolicy(); /** * This is a method used to sort a list using the STL sorting methods. * @param s1 the first shape * @param s2 the second shape */ static bool compareShapeZIndex(KoShape *s1, KoShape *s2); /** * returns the outline of the shape in the form of a path. * The outline returned will always be relative to the position() of the shape, so * moving the shape will not alter the result. The outline is used to draw the stroke * on, for example. * @returns the outline of the shape in the form of a path. */ virtual QPainterPath outline() const; /** * returns the outline of the shape in the form of a rect. * The outlineRect returned will always be relative to the position() of the shape, so * moving the shape will not alter the result. The outline is used to calculate * the boundingRect. * @returns the outline of the shape in the form of a rect. */ virtual QRectF outlineRect() const; /** * returns the outline of the shape in the form of a path for the use of painting a shadow. * * Normally this would be the same as outline() if there is a fill (background) set on the * shape and empty if not. However, a shape could reimplement this to return an outline * even if no fill is defined. A typical example of this would be the picture shape * which has a picture but almost never a background. * * @returns the outline of the shape in the form of a path. */ virtual QPainterPath shadowOutline() const; /** * Returns the currently set stroke, or 0 if there is no stroke. * @return the currently set stroke, or 0 if there is no stroke. */ KoShapeStrokeModelSP stroke() const; /** * Set a new stroke, removing the old one. The stroke inheritance becomes disabled. * @param stroke the new stroke, or 0 if there should be no stroke. */ void setStroke(KoShapeStrokeModelSP stroke); /** * @brief setInheritStroke marks a shape as inhiriting the stroke * from the parent shape. NOTE: The currently selected stroke is destroyed. * @param value true if the shape should inherit the stroke style */ void setInheritStroke(bool value); /** * @brief inheritStroke shows if the shape inherits the stroke from its parent * @return true if the shape inherits the stroke style */ bool inheritStroke() const; /** * Return the insets of the stroke. * Convenience method for KoShapeStrokeModel::strokeInsets() */ KoInsets strokeInsets() const; /// Sets the new shadow, removing the old one void setShadow(KoShapeShadow *shadow); /// Returns the currently set shadow or 0 if there is no shadow set KoShapeShadow *shadow() const; /// Sets the new border, removing the old one. void setBorder(KoBorder *border); /// Returns the currently set border or 0 if there is no border set KoBorder *border() const; /// Sets a new clip path, removing the old one void setClipPath(KoClipPath *clipPath); /// Returns the currently set clip path or 0 if there is no clip path set KoClipPath * clipPath() const; /// Sets a new clip mask, removing the old one. The mask is owned by the shape. void setClipMask(KoClipMask *clipMask); /// Returns the currently set clip mask or 0 if there is no clip mask set KoClipMask* clipMask() const; /** * Setting the shape to keep its aspect-ratio has the effect that user-scaling will * keep the width/height ratio intact so as not to distort shapes that rely on that * ratio. * @param keepAspect the new value */ void setKeepAspectRatio(bool keepAspect); /** * Setting the shape to keep its aspect-ratio has the effect that user-scaling will * keep the width/height ratio intact so as not to distort shapes that rely on that * ratio. * @return whether to keep aspect ratio of this shape */ bool keepAspectRatio() const; /** * Return the position of this shape regardless of rotation/skew/scaling and regardless of * this shape having a parent (being in a group) or not.
* @param anchor The place on the (unaltered) shape that you want the position of. * @return the point that is the absolute, centered position of this shape. */ QPointF absolutePosition(KoFlake::AnchorPosition anchor = KoFlake::Center) const; /** * Move this shape to an absolute position where the end location will be the same * regardless of the shape's rotation/skew/scaling and regardless of this shape having * a parent (being in a group) or not.
* The newPosition is going to be the center of the shape. * This has the convenient effect that:

     shape->setAbsolutePosition(QPointF(0,0));
     shape->rotate(45);
Will result in the same visual position of the shape as the opposite:
     shape->rotate(45);
     shape->setAbsolutePosition(QPointF(0,0));
* @param newPosition the new absolute center of the shape. * @param anchor The place on the (unaltered) shape that you set the position of. */ void setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor = KoFlake::Center); /** * Set a data object on the shape to be used by an application. * This is specifically useful when a shape is created in a plugin and that data from that * shape should be accessible outside the plugin. * @param userData the new user data, or 0 to delete the current one. */ void setUserData(KoShapeUserData *userData); /** * Return the current userData. */ KoShapeUserData *userData() const; /** * Return the Id of this shape, identifying the type of shape by the id of the factory. * @see KoShapeFactoryBase::shapeId() * @return the id of the shape-type */ QString shapeId() const; /** * Set the Id of this shape. A shapeFactory is expected to set the Id at creation * so applications can find out what kind of shape this is. * @see KoShapeFactoryBase::shapeId() * @param id the ID from the factory that created this shape */ void setShapeId(const QString &id); /** * Create a matrix that describes all the transformations done on this shape. * * The absolute transformation is the combined transformation of this shape * and all its parents and grandparents. * * @param converter if not null, this method uses the converter to mark the right * offsets in the current view. */ QTransform absoluteTransformation(const KoViewConverter *converter) const; /** * Applies a transformation to this shape. * * The transformation given is relative to the global coordinate system, i.e. the document. * This is a convenience function to apply a global transformation to this shape. * @see applyTransformation * * @param matrix the transformation matrix to apply */ void applyAbsoluteTransformation(const QTransform &matrix); /** * Sets a new transformation matrix describing the local transformations on this shape. * @param matrix the new transformation matrix */ void setTransformation(const QTransform &matrix); /// Returns the shapes local transformation matrix QTransform transformation() const; /** * Applies a transformation to this shape. * * The transformation given is relative to the shape coordinate system. * * @param matrix the transformation matrix to apply */ void applyTransformation(const QTransform &matrix); /** * Copy all the settings from the parameter shape and apply them to this shape. * Settings like the position and rotation to visible and locked. The parent * is a notable exclusion. * @param shape the shape to use as original */ void copySettings(const KoShape *shape); /** * Convenience method that allows people implementing paint() to use the shape * internal coordinate system directly to paint itself instead of considering the * views zoom. * @param painter the painter to alter the zoom level of. * @param converter the converter for the current views zoom. */ static void applyConversion(QPainter &painter, const KoViewConverter &converter); /** * A convenience method that creates a handles helper with applying transformations at * the same time. Please note that you shouldn't save/restore additionally. All the work * on restoring original painter's transformations is done by the helper. */ static KisHandlePainterHelper createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0); /** * @brief Transforms point from shape coordinates to document coordinates * @param point in shape coordinates * @return point in document coordinates */ QPointF shapeToDocument(const QPointF &point) const; /** * @brief Transforms rect from shape coordinates to document coordinates * @param rect in shape coordinates * @return rect in document coordinates */ QRectF shapeToDocument(const QRectF &rect) const; /** * @brief Transforms point from document coordinates to shape coordinates * @param point in document coordinates * @return point in shape coordinates */ QPointF documentToShape(const QPointF &point) const; /** * @brief Transform rect from document coordinates to shape coordinates * @param rect in document coordinates * @return rect in shape coordinates */ QRectF documentToShape(const QRectF &rect) const; /** * Returns the name of the shape. * @return the shapes name */ QString name() const; /** * Sets the name of the shape. * @param name the new shape name */ void setName(const QString &name); /** * Update the position of the shape in the tree of the KoShapeManager. */ void notifyChanged(); /** * A shape can be in a state that it is doing processing data like loading or text layout. * In this case it can be shown on screen probably partially but it should really not be printed * until it is fully done processing. * Warning! This method can be blocking for a long time * @param converter The converter * @param asynchronous If set to true the processing will can take place in a different thread and the * function will not block until the shape is finished. * In case of printing Flake will call this method from a non-main thread and only * start printing it when the in case of printing method returned. * If set to false the processing needs to be done synchronously and will * block until the result is finished. */ virtual void waitUntilReady(const KoViewConverter &converter, bool asynchronous = true) const; /// checks recursively if the shape or one of its parents is not visible or locked virtual bool isShapeEditable(bool recursive = true) const; /** * Adds a shape which depends on this shape. * Making a shape dependent on this one means it will get shapeChanged() called * on each update of this shape. * * If this shape already depends on the given shape, establishing the * dependency is refused to prevent circular dependencies. * * @param shape the shape which depends on this shape * @return true if dependency could be established, otherwise false * @see removeDependee(), hasDependee() */ bool addDependee(KoShape *shape); /** * Removes as shape depending on this shape. * @see addDependee(), hasDependee() */ void removeDependee(KoShape *shape); /// Returns if the given shape is dependent on this shape bool hasDependee(KoShape *shape) const; /// Returns list of shapes depending on this shape QList dependees() const; /// Returns additional snap data the shape wants to have snapping to virtual KoSnapData snapData() const; /** * Set additional attribute * * This can be used to attach additional attributes to a shape for attributes * that are application specific like presentation:placeholder * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * @param value The value of the attribute */ void setAdditionalAttribute(const QString &name, const QString &value); /** * Remove additional attribute * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder */ void removeAdditionalAttribute(const QString &name); /** * Check if additional attribute is set * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * * @return true if there is a attribute with prefix:tag set, false otherwise */ bool hasAdditionalAttribute(const QString &name) const; /** * Get additional attribute * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * * @return The value of the attribute if it exists or a null string if not found. */ QString additionalAttribute(const QString &name) const; void setAdditionalStyleAttribute(const char *name, const QString &value); void removeAdditionalStyleAttribute(const char *name); /** * Returns the filter effect stack of the shape * * @return the list of filter effects applied on the shape when rendering. */ KoFilterEffectStack *filterEffectStack() const; /// Sets the new filter effect stack, removing the old one void setFilterEffectStack(KoFilterEffectStack *filterEffectStack); /** * Set the property collision detection. * Setting this to true will result in calls to shapeChanged() with the CollisionDetected * parameter whenever either this or another shape is moved/rotated etc and intersects this shape. * @param detect if true detect collisions. */ void setCollisionDetection(bool detect); /** * get the property collision detection. * @returns true if collision detection is on. */ bool collisionDetection(); /** * Return the tool delegates for this shape. * In Flake a shape being selected will cause the tool manager to make available all tools that * can edit the selected shapes. In some cases selecting one shape should allow the tool to * edit a related shape be available too. The tool delegates allows this to happen by taking * all the shapes in the set into account on tool selection. * Notice that if the set is non-empty 'this' shape is no longer looked at. You can choose * to add itself to the set too. */ QSet toolDelegates() const; /** * Set the tool delegates. * @param delegates the new delegates. * @see toolDelegates() */ void setToolDelegates(const QSet &delegates); /** * Return the hyperlink for this shape. */ QString hyperLink () const; /** * Set hyperlink for this shape. * @param hyperLink name. */ void setHyperLink(const QString &hyperLink); - /** - * \internal - * Returns the private object for use within the flake lib - */ - KoShapePrivate *priv(); - public: struct KRITAFLAKE_EXPORT ShapeChangeListener { virtual ~ShapeChangeListener(); virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0; private: friend class KoShape; - friend class KoShapePrivate; void registerShape(KoShape *shape); void unregisterShape(KoShape *shape); void notifyShapeChangedImpl(ChangeType type, KoShape *shape); QList m_registeredShapes; }; void addShapeChangeListener(ShapeChangeListener *listener); void removeShapeChangeListener(ShapeChangeListener *listener); +protected: + QList listeners() const; + void setSizeImpl(const QSizeF &size) const; + public: static QList linearizeSubtree(const QList &shapes); protected: - /// constructor - KoShape(KoShapePrivate *); + KoShape(const KoShape &rhs); /* ** loading saving helper methods */ /// attributes from ODF 1.1 chapter 9.2.15 Common Drawing Shape Attributes enum OdfAttribute { OdfTransformation = 1, ///< Store transformation information OdfSize = 2, ///< Store size information OdfPosition = 8, ///< Store position OdfAdditionalAttributes = 4, ///< Store additional attributes of the shape OdfCommonChildElements = 16, ///< Event actions and connection points OdfLayer = 64, ///< Store layer name OdfStyle = 128, ///< Store the style OdfId = 256, ///< Store the unique ID OdfName = 512, ///< Store the name of the shape OdfZIndex = 1024, ///< Store the z-index OdfViewbox = 2048, ///< Store the viewbox /// A mask for all mandatory attributes OdfMandatories = OdfLayer | OdfStyle | OdfId | OdfName | OdfZIndex, /// A mask for geometry attributes OdfGeometry = OdfPosition | OdfSize, /// A mask for all the attributes OdfAllAttributes = OdfTransformation | OdfGeometry | OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements }; /** * This method is used during loading of the shape to load common attributes * * @param context the KoShapeLoadingContext used for loading * @param element element which represents the shape in odf * @param attributes a number of OdfAttribute items to state which attributes to load. */ bool loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes); /** * Parses the transformation attribute from the given string * @param transform the transform attribute string * @return the resulting transformation matrix */ QTransform parseOdfTransform(const QString &transform); /** * @brief Saves the style used for the shape * * This method fills the given style object with the stroke and * background properties and then adds the style to the context. * * @param style the style object to fill * @param context used for saving * @return the name of the style * @see saveOdf */ virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const; /** * Loads the stroke and fill style from the given element. * * @param element the xml element to load the style from * @param context the loading context used for loading */ virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context); /// Loads the stroke style KoShapeStrokeModelSP loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const; /// Loads the fill style QSharedPointer loadOdfFill(KoShapeLoadingContext &context) const; /// Loads the connection points void loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context); /// Loads the clip contour void loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor); /* ** end loading saving */ /** * A hook that allows inheriting classes to do something after a KoShape property changed * This is called whenever the shape, position rotation or scale properties were altered. * @param type an indicator which type was changed. * @param shape the shape. */ virtual void shapeChanged(ChangeType type, KoShape *shape = 0); /// return the current matrix that contains the rotation/scale/position of this shape QTransform transform() const; - KoShapePrivate *d_ptr; +private: + class Private; + QSharedDataPointer d; + +protected: + /** + * Notify the shape that a change was done. To be used by inheriting shapes. + * @param type the change type + */ + void shapeChangedPriv(KoShape::ChangeType type); private: - Q_DECLARE_PRIVATE(KoShape) + void addShapeManager(KoShapeManager *manager); + void removeShapeManager(KoShapeManager *manager); + friend class KoShapeManager; }; Q_DECLARE_METATYPE(KoShape*) #endif diff --git a/libs/flake/KoShapeBackground.cpp b/libs/flake/KoShapeBackground.cpp index b9086f3fc6..277c5641f2 100644 --- a/libs/flake/KoShapeBackground.cpp +++ b/libs/flake/KoShapeBackground.cpp @@ -1,52 +1,33 @@ /* This file is part of the KDE project * Copyright (C) 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 "KoShapeBackground.h" -#include "KoShapeBackground_p.h" - -KoShapeBackgroundPrivate::KoShapeBackgroundPrivate() -{ -} - -KoShapeBackgroundPrivate::~KoShapeBackgroundPrivate() -{ -} - -KoShapeBackground::KoShapeBackground(KoShapeBackgroundPrivate &dd) - :d_ptr(&dd) -{ -} - - - KoShapeBackground::KoShapeBackground() - : d_ptr(new KoShapeBackgroundPrivate()) { } KoShapeBackground::~KoShapeBackground() { - delete d_ptr; } bool KoShapeBackground::hasTransparency() const { return false; } diff --git a/libs/flake/KoShapeBackground.h b/libs/flake/KoShapeBackground.h index 91a3d45d46..ed5aa2afc2 100644 --- a/libs/flake/KoShapeBackground.h +++ b/libs/flake/KoShapeBackground.h @@ -1,75 +1,69 @@ /* This file is part of the KDE project * Copyright (C) 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. */ #ifndef KOSHAPEBACKGROUND_H #define KOSHAPEBACKGROUND_H #include "kritaflake_export.h" #include class QSizeF; class QPainter; class QPainterPath; class KoGenStyle; class KoShapeSavingContext; class KoOdfLoadingContext; -class KoShapeBackgroundPrivate; class KoShapePaintingContext; class KoViewConverter; /** * This is the base class for shape backgrounds. * Derived classes are used to paint the background of * a shape within a given painter path. */ class KRITAFLAKE_EXPORT KoShapeBackground { public: KoShapeBackground(); virtual ~KoShapeBackground(); /// Paints the background using the given fill path virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const = 0; /// Returns if the background has some transparency. virtual bool hasTransparency() const; virtual bool compareTo(const KoShapeBackground *other) const = 0; /** * Fills the style object * @param style object * @param context used for saving */ virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) = 0; /// load background from odf styles virtual bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) = 0; -protected: - KoShapeBackground(KoShapeBackgroundPrivate &); - KoShapeBackgroundPrivate *d_ptr; -private: - Q_DECLARE_PRIVATE(KoShapeBackground) - + virtual explicit operator bool() const { return true; } }; #endif // KOSHAPEBACKGROUND_H diff --git a/libs/flake/KoShapeBackground_p.h b/libs/flake/KoShapeBackground_p.h deleted file mode 100644 index e0b148db08..0000000000 --- a/libs/flake/KoShapeBackground_p.h +++ /dev/null @@ -1,30 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 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. - */ - -#ifndef KoShapeBackgroundPrivate_H -#define KoShapeBackgroundPrivate_H - -class KoShapeBackgroundPrivate -{ -public: - KoShapeBackgroundPrivate(); - virtual ~KoShapeBackgroundPrivate(); -}; - -#endif diff --git a/libs/flake/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp index 0c4dc6b9cf..d00a82171d 100644 --- a/libs/flake/KoShapeContainer.cpp +++ b/libs/flake/KoShapeContainer.cpp @@ -1,234 +1,231 @@ /* 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) +KoShapeContainer::Private::Private(KoShapeContainer *q) + : shapeInterface(q) + , model(0) { } -KoShapeContainerPrivate::~KoShapeContainerPrivate() +KoShapeContainer::Private::~Private() { delete model; } -KoShapeContainerPrivate::KoShapeContainerPrivate(const KoShapeContainerPrivate &rhs, KoShapeContainer *q) - : KoShapePrivate(rhs, q), - shapeInterface(q), - model(0) +KoShapeContainer::Private::Private(const KoShapeContainer::Private &rhs, KoShapeContainer *q) + : shapeInterface(q) + , model(0) { + Q_UNUSED(rhs); } KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model) - : KoShape(new KoShapeContainerPrivate(this)) + : KoShape() + , d(new Private(this)) { - Q_D(KoShapeContainer); d->model = model; } -KoShapeContainer::KoShapeContainer(KoShapeContainerPrivate *dd) - : KoShape(dd) +KoShapeContainer::KoShapeContainer(const KoShapeContainer &rhs) + : KoShape(rhs) + , d(new Private(*(rhs.d.data()), this)) { - 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()) { - 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 +int KoShapeContainer::shapeCount() const { - Q_D(const KoShapeContainer); if (d->model == 0) return 0; return d->model->count(); } 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 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)) 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; } +void KoShapeContainer::setModel(KoShapeContainerModel *model) +{ + d->model = model; +} + +void KoShapeContainer::setModelInit(KoShapeContainerModel *model) +{ + setModel(model); + // 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()) { + if (shape) { // Note: shape can be 0 because not all shapes + // implement cloneShape, e.g. the text shape. + shape->setParent(this); + } + } + } +} + 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(); + KoShapeContainer::Private * const d = q->d.data(); 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(); + KoShapeContainer::Private * const d = q->d.data(); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->model); KIS_SAFE_ASSERT_RECOVER_RETURN(d->model->shapes().contains(shape)); d->model->shapeToBeRemovedFromHierarchy(shape, q); d->model->remove(shape); KoShapeContainer *grandparent = q->parent(); if (grandparent) { grandparent->model()->childChanged(q, KoShape::ChildChanged); } } diff --git a/libs/flake/KoShapeContainer.h b/libs/flake/KoShapeContainer.h index 3f31b9d8fc..3e33f41e1f 100644 --- a/libs/flake/KoShapeContainer.h +++ b/libs/flake/KoShapeContainer.h @@ -1,252 +1,264 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECONTAINER_H #define KOSHAPECONTAINER_H #include "KoShape.h" #include #include "kritaflake_export.h" class QPainter; class KoShapeContainerModel; class KoShapeContainerPrivate; class KoViewConverter; /** * This is the base class that all Flake group-shapes are based on. * Extending from this class allows you to have child-shapes. * Like the KoShape class, this shape is a visible class with * a position and a size. It can paint itself as well if you implement * the paintComponent() method. * *

The most important feature of this class is that you can make * other KoShape classes to be children of this container. * *

The effect of grouping those shapes is that their position * is relative to the position of the container. Move the container and * all children move with it. * *

Each child can optionally be said to be 'clipped' by the container. * This feature will give the effect that if the child has a size and * position outside the container, parts outside the container will not be shown. * This is especially useful * for showing cutouts of content, like images, without changing the actual content. * *

For so called clipped children any modification made to the container is * propagated to the child. This includes rotation as well as scaling * and shearing. * *

Maintaining the list of children can be done using the supplied methods * addChild() and removeChild(). However, they only forward their requests to the * data model KoShapeContainerModel and if you provide a custom implementation * of that model any means can be used to maintain a list of children, as long as * you will take care to register them with the appropriate shape manager. * *

An example usage where a custom model might be useful is when you have a * container for text areas which are split into columns. If you resize the container * and the width of the individual columns gets too small, the model can choose to * remove a child or add one when the width allows another column. */ class KRITAFLAKE_EXPORT KoShapeContainer : public KoShape { public: /** * Constructor with custom model to be used for maintaining the list of children. * For all the normal cases you don't need a custom model. Only when you want to respond * to moves of the container to do something special, or disable one of the features the * container normally has (like clipping). Use the default constructor in those cases. * @param model the custom model to be used for maintaining the list of children. */ explicit KoShapeContainer(KoShapeContainerModel *model = 0); /** * Destructor for the shape container. * All children will be orphaned by calling a KoShape::setParent(0) */ ~KoShapeContainer() override; /** * Add a child to this container. * * This container will NOT take over ownership of the shape. The caller or those creating * the shape is responsible to delete it if not needed any longer. * * @param shape the child to be managed in the container. */ void addShape(KoShape *shape); /** * Remove a child to be completely separated from the container. * * The shape will only be removed from this container but not be deleted. * * @param shape the child to be removed. */ void removeShape(KoShape *shape); /** * Return the current number of children registered. * @return the current number of children registered. */ int shapeCount() const; /** * Set the argument child to have its 'clipping' property set. * * A shape that is clipped by the container will have its visible portion * limited to the area where it intersects with the container. * If a shape is positioned or sized such that it would be painted outside * of the KoShape::outline() of its parent container, setting this property * to true will clip the shape painting to the container outline. * * @param child the child for which the property will be changed. * @param clipping the property */ void setClipped(const KoShape *child, bool clipping); /** * Returns if the argument child has its 'clipping' property set. * * A shape that is clipped by the container will have its visible portion * limited to the area where it intersects with the container. * If a shape is positioned or sized such that it would be painted outside * of the KoShape::outline() of its parent container, setting this property * to true will clip the shape painting to the container outline. * * @return if the argument child has its 'clipping' property set. * @param child the child for which the property will be returned. */ bool isClipped(const KoShape *child) const; /** * Set the shape to inherit the container transform. * * A shape that inherits the transform of the parent container will have its * share / rotation / skew etc be calculated as being the product of both its * own local transformation and also that of its parent container. * If you set this to true and rotate the container, the shape will get that * rotation as well automatically. * * @param shape the shape for which the property will be changed. * @param inherit the new value */ void setInheritsTransform(const KoShape *shape, bool inherit); /** * Returns if the shape inherits the container transform. * * A shape that inherits the transform of the parent container will have its * share / rotation / skew etc be calculated as being the product of both its * own local transformation and also that of its parent container. * If you set this to true and rotate the container, the shape will get that * rotation as well automatically. * * @return if the argument shape has its 'inherits transform' property set. * @param shape the shape for which the property will be returned. */ bool inheritsTransform(const KoShape *shape) const; /// reimplemented void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; /** * @brief Paint the component * Implement this method to allow the shape to paint itself, just like the KoShape::paint() * method does. * * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @param paintcontext the painting context * @see applyConversion() */ virtual void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0; using KoShape::update; /// reimplemented void update() const override; /** * Return the list of all child shapes. * @return the list of all child shapes */ QList shapes() const; /** * return the model for this container */ KoShapeContainerModel *model() const; +protected: + + /** + * set the model for this container + */ + void setModel(KoShapeContainerModel *model); + /** + * set the model, and take control of all its children + */ + void setModelInit(KoShapeContainerModel *model); + +public: /** * A special interface for KoShape to use during setParent call. Don't use * these method directly for managing shapes hierarchy! Use shape->setParent() * instead. */ struct ShapeInterface { ShapeInterface(KoShapeContainer *_q); /** * Add a child to this container. * * This container will NOT take over ownership of the shape. The caller or those creating * the shape is responsible to delete it if not needed any longer. * * @param shape the child to be managed in the container. */ void addShape(KoShape *shape); /** * Remove a child to be completely separated from the container. * * The shape will only be removed from this container but not be deleted. * * @param shape the child to be removed. */ void removeShape(KoShape *shape); protected: KoShapeContainer *q; }; ShapeInterface* shapeInterface(); protected: + KoShapeContainer(const KoShapeContainer &rhs); + /** * This hook is for inheriting classes that need to do something on adding/removing * of children. * This method will be called just after the child has been added/removed. * The default implementation is empty. */ virtual void shapeCountChanged() { } void shapeChanged(ChangeType type, KoShape *shape = 0) override; - /// constructor - KoShapeContainer(KoShapeContainerPrivate *); - private: - Q_DECLARE_PRIVATE(KoShapeContainer) + class Private; + QScopedPointer d; }; #endif diff --git a/libs/flake/KoShapeContainer_p.h b/libs/flake/KoShapeContainer_p.h index b656793649..7564ff7e29 100644 --- a/libs/flake/KoShapeContainer_p.h +++ b/libs/flake/KoShapeContainer_p.h @@ -1,43 +1,42 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECONTAINERPRIVATE_H #define KOSHAPECONTAINERPRIVATE_H -#include "KoShape_p.h" #include "KoShapeContainer.h" #include "kritaflake_export.h" class KoShapeContainerModel; /** * \internal used private d-pointer class for the \a KoShapeContainer class. */ -class KRITAFLAKE_EXPORT KoShapeContainerPrivate : public KoShapePrivate +class KRITAFLAKE_EXPORT KoShapeContainer::Private { public: - explicit KoShapeContainerPrivate(KoShapeContainer *q); - ~KoShapeContainerPrivate() override; + explicit Private(KoShapeContainer *q); + virtual ~Private(); - KoShapeContainerPrivate(const KoShapeContainerPrivate &rhs, KoShapeContainer *q); + Private(const Private &rhs, KoShapeContainer *q); KoShapeContainer::ShapeInterface shapeInterface; KoShapeContainerModel *model; }; #endif diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp index 437428b1ca..2600f49203 100644 --- a/libs/flake/KoShapeGroup.cpp +++ b/libs/flake/KoShapeGroup.cpp @@ -1,286 +1,276 @@ /* This file is part of the KDE project * Copyright (C) 2006 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 "KoShapeGroup.h" #include "KoShapeContainerModel.h" #include "KoShapeContainer_p.h" #include "KoShapeLayer.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include "KoShapeRegistry.h" #include "KoShapeStrokeModel.h" #include "KoShapeShadow.h" #include "KoInsets.h" #include #include class ShapeGroupContainerModel : public SimpleShapeContainerModel { public: ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {} ~ShapeGroupContainerModel() override {} ShapeGroupContainerModel(const ShapeGroupContainerModel &rhs, KoShapeGroup *group) : SimpleShapeContainerModel(rhs), m_group(group) { } void add(KoShape *child) override { SimpleShapeContainerModel::add(child); m_group->invalidateSizeCache(); } void remove(KoShape *child) override { SimpleShapeContainerModel::remove(child); m_group->invalidateSizeCache(); } void childChanged(KoShape *shape, KoShape::ChangeType type) override { SimpleShapeContainerModel::childChanged(shape, type); //debugFlake << type; switch (type) { case KoShape::PositionChanged: case KoShape::RotationChanged: case KoShape::ScaleChanged: case KoShape::ShearChanged: case KoShape::SizeChanged: case KoShape::GenericMatrixChange: case KoShape::ParameterChanged: case KoShape::ClipPathChanged : m_group->invalidateSizeCache(); break; default: break; } } private: // members KoShapeGroup * m_group; }; -class KoShapeGroupPrivate : public KoShapeContainerPrivate +class KoShapeGroup::Private { public: - KoShapeGroupPrivate(KoShapeGroup *q) - : KoShapeContainerPrivate(q) - { - model = new ShapeGroupContainerModel(q); - } + Private() {} - KoShapeGroupPrivate(const KoShapeGroupPrivate &rhs, KoShapeGroup *q) - : KoShapeContainerPrivate(rhs, q) - { - ShapeGroupContainerModel *otherModel = dynamic_cast(rhs.model); - KIS_ASSERT_RECOVER_RETURN(otherModel); - model = new ShapeGroupContainerModel(*otherModel, q); - } + Private(const Private &) {} - ~KoShapeGroupPrivate() override - { - } + virtual ~Private() = default; mutable QRectF savedOutlineRect; mutable bool sizeCached = false; - void tryUpdateCachedSize() const; - - Q_DECLARE_PUBLIC(KoShapeGroup) }; KoShapeGroup::KoShapeGroup() - : KoShapeContainer(new KoShapeGroupPrivate(this)) + : KoShapeContainer() + , d(new Private) { + setModelInit(new ShapeGroupContainerModel(this)); } KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs) - : KoShapeContainer(new KoShapeGroupPrivate(*rhs.d_func(), this)) + : KoShapeContainer(rhs) + , d(new Private(*rhs.d)) { + ShapeGroupContainerModel *otherModel = dynamic_cast(rhs.model()); + KIS_ASSERT_RECOVER_RETURN(otherModel); + setModelInit(new ShapeGroupContainerModel(*otherModel, this)); } KoShapeGroup::~KoShapeGroup() { + /** + * HACK alert: model will use KoShapeGroup::invalidateSizeCache(), which uses + * KoShapeGroup's d-pointer. We have to manually remove child shapes from the + * model in the destructor of KoShapeGroup as the instance d is no longer accessible + * since ~KoShapeGroup() is executed + */ + model()->deleteOwnedShapes(); } KoShape *KoShapeGroup::cloneShape() const { return new KoShapeGroup(*this); } void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } bool KoShapeGroup::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } -void KoShapeGroupPrivate::tryUpdateCachedSize() const +void KoShapeGroup::tryUpdateCachedSize() const { - Q_Q(const KoShapeGroup); - - if (!sizeCached) { + if (!d->sizeCached) { QRectF bound; - Q_FOREACH (KoShape *shape, q->shapes()) { + Q_FOREACH (KoShape *shape, shapes()) { bound |= shape->transformation().mapRect(shape->outlineRect()); } - savedOutlineRect = bound; - size = bound.size(); - sizeCached = true; + d->savedOutlineRect = bound; + KoShape::setSizeImpl(bound.size()); + d->sizeCached = true; } } QSizeF KoShapeGroup::size() const { - Q_D(const KoShapeGroup); - - d->tryUpdateCachedSize(); - return d->size; + tryUpdateCachedSize(); + return KoShape::size(); } void KoShapeGroup::setSize(const QSizeF &size) { QSizeF oldSize = this->size(); if (!shapeCount() || oldSize.isNull()) return; const QTransform scale = QTransform::fromScale(size.width() / oldSize.width(), size.height() / oldSize.height()); setTransformation(scale * transformation()); KoShapeContainer::setSize(size); } QRectF KoShapeGroup::outlineRect() const { - Q_D(const KoShapeGroup); - - d->tryUpdateCachedSize(); + tryUpdateCachedSize(); return d->savedOutlineRect; } QRectF KoShapeGroup::boundingRect() const { QRectF groupBound = KoShape::boundingRect(shapes()); if (shadow()) { KoInsets insets; shadow()->insets(insets); groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } return groupBound; } void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:g"); saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes); context.xmlWriter().addAttribute("svg:y", position().y()); QList shapes = this->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape* shape, shapes) { shape->saveOdf(context); } saveOdfCommonChildElements(context); context.xmlWriter().endElement(); } bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { - Q_D(KoShapeGroup); loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes | OdfCommonChildElements); KoXmlElement child; QMap usedLayers; forEachElement(child, element) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { KoShapeLayer *layer = dynamic_cast(shape->parent()); if (layer) { usedLayers[layer]++; } addShape(shape); } } KoShapeLayer *parent = 0; int maxUseCount = 0; // find most used layer and use this as parent for the group for (QMap::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { if (it.value() > maxUseCount) { maxUseCount = it.value(); parent = it.key(); } } setParent(parent); QRectF bound; bool boundInitialized = false; Q_FOREACH (KoShape * shape, shapes()) { if (! boundInitialized) { bound = shape->boundingRect(); boundInitialized = true; } else bound = bound.united(shape->boundingRect()); } setSize(bound.size()); d->sizeCached = true; setPosition(bound.topLeft()); Q_FOREACH (KoShape * shape, shapes()) shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft()); return true; } void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); switch (type) { case KoShape::StrokeChanged: break; default: break; } invalidateSizeCache(); } void KoShapeGroup::invalidateSizeCache() { - Q_D(KoShapeGroup); d->sizeCached = false; } diff --git a/libs/flake/KoShapeGroup.h b/libs/flake/KoShapeGroup.h index ffb859c6b1..0ddf7e9a06 100644 --- a/libs/flake/KoShapeGroup.h +++ b/libs/flake/KoShapeGroup.h @@ -1,93 +1,96 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPEGROUP_H #define KOSHAPEGROUP_H #include "KoShapeContainer.h" #include #include "kritaflake_export.h" class KoShapeSavingContext; class KoShapeLoadingContext; class KoShapeGroupPrivate; /** * Provide grouping for shapes. * The group shape allows you to add children which will then be grouped in selections * and actions. *

If you have a set of shapes that together make up a bigger shape it is often * useful to group them together so the user will perceive the different shapes as * actually being one. This means that if the user clicks on one shape, all shapes * in the group will be selected at once, making the tools that works on * selections alter all of them at the same time. * *

Note that while this object is also a shape, it is not actually visible and the user * can't interact with it. * *

WARNING: this class is NOT threadsafe, it caches the size in an unsafe way */ class KRITAFLAKE_EXPORT KoShapeGroup : public KoShapeContainer { public: /// Constructor KoShapeGroup(); /// destructor ~KoShapeGroup() override; KoShape* cloneShape() const override; /// This implementation is empty since a group is itself not visible. void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; /// always returns false since the group itself can't be selected or hit bool hitTest(const QPointF &position) const override; QSizeF size() const override; void setSize(const QSizeF &size) override; QRectF outlineRect() const override; /// a group's boundingRect QRectF boundingRect() const override; /// reimplemented from KoShape void saveOdf(KoShapeSavingContext &context) const override; // reimplemented bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; private: friend class ShapeGroupContainerModel; /** * @brief Invalidate the size cache of the group * * The group shape caches the size of itself as it can be quite expensive to recalculate * the size if there are a lot of subshapes. This function is called when the cache needs * to be invalidated. */ void invalidateSizeCache(); private: KoShapeGroup(const KoShapeGroup &rhs); private: + void tryUpdateCachedSize() const; + void shapeChanged(ChangeType type, KoShape *shape = 0) override; - Q_DECLARE_PRIVATE(KoShapeGroup) + class Private; + QScopedPointer d; }; #endif diff --git a/libs/flake/KoShapeLayer.h b/libs/flake/KoShapeLayer.h index 4f1fa6a800..8ade3662e0 100644 --- a/libs/flake/KoShapeLayer.h +++ b/libs/flake/KoShapeLayer.h @@ -1,59 +1,54 @@ /* This file is part of the KDE project Copyright (C) 2006-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. */ #ifndef __KOSHAPELAYER_H__ #define __KOSHAPELAYER_H__ #include "KoShapeContainer.h" #include "kritaflake_export.h" -class KoShapeLayerPrivate; - /** * Provides arranging shapes into layers. * This makes it possible to have a higher key of a number of objects * in a document. * A layer is always invisible and unselectable. */ class KRITAFLAKE_EXPORT KoShapeLayer : public KoShapeContainer { public: /// The default constructor KoShapeLayer(); /** * Constructor with custom model * @param model the custom modem */ explicit KoShapeLayer(KoShapeContainerModel *model); /** * Empty implementation, as the layer itself is not visible */ void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; bool hitTest(const QPointF &position) const override; QRectF boundingRect() const override; void saveOdf(KoShapeSavingContext & context) const override; bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) override; - -private: - Q_DECLARE_PRIVATE(KoShapeLayer) }; #endif // __KOSHAPELAYER_H__ diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index d4f3a2e26d..ee88220e94 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,634 +1,634 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 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 "KoShapeManager.h" #include "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoClipMaskPainter.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include "KisQPainterStateSaver.h" #include "KoSvgTextChunkShape.h" #include "KoSvgTextShape.h" #include #include #include #include #include "kis_painting_tweaks.h" bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape) { // FIXME: make more general! return !dynamic_cast(shape) && !dynamic_cast(shape) && !(dynamic_cast(shape) && !dynamic_cast(shape)); } void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; Q_FOREACH (KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { if (!shapeUsedInRenderingTree(shape)) continue; tree.remove(shape); QRectF br(shape->boundingRect()); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. foreach (KoShape *shape, aggregate4update) { detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible(false)) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); /** * Shape manager uses signal compressors with timers, therefore * it might handle queued signals, therefore it should belong * to the GUI thread. */ this->moveToThread(qApp->thread()); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); // see a comment in another constructor this->moveToThread(qApp->thread()); } void KoShapeManager::Private::unlinkFromShapesRecursively(const QList &shapes) { Q_FOREACH (KoShape *shape, shapes) { - shape->priv()->removeShapeManager(q); + shape->removeShapeManager(q); KoShapeContainer *container = dynamic_cast(shape); if (container) { unlinkFromShapesRecursively(container->shapes()); } } } KoShapeManager::~KoShapeManager() { d->unlinkFromShapesRecursively(d->shapes); d->shapes.clear(); delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); d->unlinkFromShapesRecursively(d->shapes); d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { if (d->shapes.contains(shape)) return; - shape->priv()->addShapeManager(this); + shape->addShapeManager(this); d->shapes.append(shape); if (d->shapeUsedInRenderingTree(shape)) { QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } void KoShapeManager::remove(KoShape *shape) { Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); shape->update(); - shape->priv()->removeShapeManager(this); + shape->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); if (d->shapeUsedInRenderingTree(shape)) { d->tree.remove(shape); } d->shapes.removeAll(shape); // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } } KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q) : q(_q) { } void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape) { q->d->selection->deselect(shape); q->d->aggregate4update.remove(shape); // we cannot access RTTI of the semi-destructed shape, so just // unlink it lazily if (q->d->tree.contains(shape)) { q->d->tree.remove(shape); } q->d->shapes.removeAll(shape); } KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface() { return &d->shapeInterface; } void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter)); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = shapes(); warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible()) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME foreach (KoShape *shape, sortedShapes) { renderSingleShape(shape, painter, converter, paintContext); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // apply transformation painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); // paint the shape paintShape(shape, painter, converter, paintContext); } void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect())); shapePainter = clipMaskPainter->shapePainter(); } /** * We expect the shape to save/restore the painter's state itself. Such design was not * not always here, so we need a period of sanity checks to ensure all the shapes are * ported correctly. */ const QTransform sanityCheckTransformSaved = shapePainter->transform(); shape->paint(*shapePainter, converter, paintContext); shape->paintStroke(*shapePainter, converter, paintContext); KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) { shapePainter->setTransform(sanityCheckTransformSaved); } if (clipMask) { shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); } } else { // TODO: clipping mask is not implemented for this case! // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); Private::paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); shape->paintStroke(imagePainter, converter, paintContext); imagePainter.restore(); imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QList inputImages; Q_FOREACH (const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QList sortedShapes(d->tree.contains(position)); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible()) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; break; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode) { d->updateTree(); QList shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect)); for (int count = shapes.count() - 1; count >= 0; count--) { KoShape *shape = shapes.at(count); if (omitHiddenShapes && !shape->isVisible()) { shapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) { shapes.removeAt(count); } else if (containedMode) { QPainterPath containingPath; containingPath.addRect(rect); if (!containingPath.contains(outline)) { shapes.removeAt(count); } } } } return shapes; } void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles) { d->canvas->updateCanvas(rect); if (selectionHandles && d->selection->isSelected(shape)) { if (d->canvas->toolProxy()) d->canvas->toolProxy()->repaintDecorations(); } } void KoShapeManager::notifyShapeChanged(KoShape *shape) { Q_ASSERT(shape); if (d->aggregate4update.contains(shape)) { return; } const bool wasEmpty = d->aggregate4update.isEmpty(); d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); KoShapeContainer *container = dynamic_cast(shape); if (container) { Q_FOREACH (KoShape *child, container->shapes()) notifyShapeChanged(child); } if (wasEmpty && !d->aggregate4update.isEmpty()) { d->updateTreeCompressor.start(); } } QList KoShapeManager::shapes() const { return d->shapes; } QList KoShapeManager::topLevelShapes() const { QList shapes; // get all toplevel shapes Q_FOREACH (KoShape *shape, d->shapes) { if (!shape->parent() || dynamic_cast(shape->parent())) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h index 484bf36084..6e9e9e02ee 100644 --- a/libs/flake/KoShapeManager_p.h +++ b/libs/flake/KoShapeManager_p.h @@ -1,124 +1,124 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 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. */ #ifndef KoShapeManager_p_h #define KoShapeManager_p_h #include "KoSelection.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeManager.h" #include #include "kis_thread_safe_signal_compressor.h" class KoCanvasBase; class KoShapeGroup; class KoShapePaintingContext; class QPainter; class Q_DECL_HIDDEN KoShapeManager::Private { public: Private(KoShapeManager *shapeManager, KoCanvasBase *c) : selection(new KoSelection(shapeManager)), canvas(c), tree(4, 2), q(shapeManager), shapeInterface(shapeManager), updateTreeCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { connect(&updateTreeCompressor, SIGNAL(timeout()), q, SLOT(updateTree())); } ~Private() { delete selection; } /** * Update the tree when there are shapes in m_aggregate4update. This is done so not all * updates to the tree are done when they are asked for but when they are needed. */ void updateTree(); /** * Returns whether the shape should be added to the RTree for collision and ROI * detection. */ bool shapeUsedInRenderingTree(KoShape *shape); /** * Recursively detach the shapes from this shape manager */ void unlinkFromShapesRecursively(const QList &shapes); /** * Recursively paints the given group shape to the specified painter * This is needed for filter effects on group shapes where the filter effect * applies to all the children of the group shape at once */ static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); class DetectCollision { public: DetectCollision() {} void detect(KoRTree &tree, KoShape *s, int prevZIndex) { Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) { bool isChild = false; KoShapeContainer *parent = s->parent(); while (parent && !isChild) { if (parent == shape) isChild = true; parent = parent->parent(); } if (isChild) continue; if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex()) // Moving a shape will only make it collide with shapes below it. continue; if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape)) shapesWithCollisionDetection.append(shape); } } void fireSignals() { Q_FOREACH (KoShape *shape, shapesWithCollisionDetection) - shape->priv()->shapeChanged(KoShape::CollisionDetected); + shape->shapeChangedPriv(KoShape::CollisionDetected); } private: QList shapesWithCollisionDetection; }; QList shapes; KoSelection *selection; KoCanvasBase *canvas; KoRTree tree; QSet aggregate4update; QHash shapeIndexesBeforeUpdate; KoShapeManager *q; KoShapeManager::ShapeInterface shapeInterface; KisThreadSafeSignalCompressor updateTreeCompressor; }; #endif diff --git a/libs/flake/KoShape_p.h b/libs/flake/KoShape_p.h index 09c6fb955c..7f4a4fc3ba 100644 --- a/libs/flake/KoShape_p.h +++ b/libs/flake/KoShape_p.h @@ -1,128 +1,117 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * 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. */ #ifndef KOSHAPEPRIVATE_H #define KOSHAPEPRIVATE_H #include "KoShape.h" #include #include #include #include +#include #include class KoBorder; class KoShapeManager; -class KoShapePrivate +class KoShape::Private : public QSharedData { public: - explicit KoShapePrivate(KoShape *shape); - virtual ~KoShapePrivate(); + explicit Private(); + virtual ~Private(); - explicit KoShapePrivate(const KoShapePrivate &rhs, KoShape *q); - - /** - * Notify the shape that a change was done. To be used by inheriting shapes. - * @param type the change type - */ - void shapeChanged(KoShape::ChangeType type); - - void addShapeManager(KoShapeManager *manager); - void removeShapeManager(KoShapeManager *manager); + explicit Private(const Private &rhs); /** * Fills the style stack and returns the value of the given style property (e.g fill, stroke). */ static QString getStyleProperty(const char *property, KoShapeLoadingContext &context); /// Loads the shadow style KoShapeShadow *loadOdfShadow(KoShapeLoadingContext &context) const; // Loads the border style. KoBorder *loadOdfBorder(KoShapeLoadingContext &context) const; + public: // Members - KoShape *q_ptr; // Points the shape that owns this class. - mutable QSizeF size; // size in pt QString shapeId; QString name; ///< the shapes names QTransform localMatrix; ///< the shapes local transformation matrix KoConnectionPoints connectors; ///< glue point id to data mapping KoShapeContainer *parent; QSet shapeManagers; QSet toolDelegates; QScopedPointer userData; QSharedPointer stroke; ///< points to a stroke, or 0 if there is no stroke QSharedPointer fill; ///< Stands for the background color / fill etc. bool inheritBackground = false; bool inheritStroke = false; QList dependees; ///< list of shape dependent on this shape QList listeners; KoShapeShadow * shadow; ///< the current shape shadow KoBorder *border; ///< the current shape border + // XXX: change this to instance instead of pointer QScopedPointer clipPath; ///< the current clip path QScopedPointer clipMask; ///< the current clip mask QMap additionalAttributes; QMap additionalStyleAttributes; KoFilterEffectStack *filterEffectStack; ///< stack of filter effects applied to the shape qreal transparency; ///< the shapes transparency QString hyperLink; //hyperlink for this shape int zIndex : 16; // keep maxZIndex in sync! int runThrough : 16; int visible : 1; int printable : 1; int geometryProtected : 1; int keepAspect : 1; int selectable : 1; int detectCollision : 1; int protectContent : 1; KoShape::TextRunAroundSide textRunAroundSide; qreal textRunAroundDistanceLeft; qreal textRunAroundDistanceTop; qreal textRunAroundDistanceRight; qreal textRunAroundDistanceBottom; qreal textRunAroundThreshold; KoShape::TextRunAroundContour textRunAroundContour; - public: /// Connection point converters /// Convert connection point position from shape coordinates, taking alignment into account void convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const; /// Convert connection point position to shape coordinates, taking alignment into account void convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const; - - Q_DECLARE_PUBLIC(KoShape) }; #endif diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index 6f5480edc0..55d4a4b29a 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,473 +1,496 @@ /* 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) : parent(p) { scrollTimer.setInterval(100); } 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 (!isToolPressed) 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->isShapeEditable()) 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(); // this tries to make sure another mouse press event doesn't happen // before a release event happens if (d->isToolPressed) { mouseReleaseEvent(ev); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(ev); } d->isToolPressed = false; 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(); } d->isToolPressed = true; } 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(); } d->isToolPressed = false; } 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(); d->isToolPressed = false; } 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 KoToolProxy::touchEvent(QTouchEvent* event, const QPointF& point) +{ + // only one "touchpoint" events should be here + KoPointerEvent ev(event, point); + + if (!d->activeTool) return; + + switch (event->touchPointStates()) + { + case Qt::TouchPointPressed: + d->activeTool->mousePressEvent(&ev); + break; + case Qt::TouchPointMoved: + d->activeTool->mouseMoveEvent(&ev); + break; + case Qt::TouchPointReleased: + d->activeTool->mouseReleaseEvent(&ev); + break; + default: // don't care + ; + } +} + void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } 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; if (d->activeTool && d->isActiveLayerEditable()) { success = d->activeTool->paste(); } 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/KoToolProxy.h b/libs/flake/KoToolProxy.h index 64999d31f5..bef679cd28 100644 --- a/libs/flake/KoToolProxy.h +++ b/libs/flake/KoToolProxy.h @@ -1,184 +1,186 @@ /* This file is part of the KDE project * * Copyright (c) 2006, 2010 Boudewijn Rempt * Copyright (C) 2006-2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_TOOL_PROXY_H_ #define _KO_TOOL_PROXY_H_ #include "kritaflake_export.h" #include #include class QAction; class QAction; class QMouseEvent; class QKeyEvent; class QWheelEvent; class QTabletEvent; class KoCanvasBase; class KoViewConverter; class KoToolBase; class KoToolProxyPrivate; class QInputMethodEvent; class KoPointerEvent; class QDragMoveEvent; class QDragLeaveEvent; class QDropEvent; class QTouchEvent; class QPainter; class QPointF; class QMenu; /** * Tool proxy object which allows an application to address the current tool. * * Applications typically have a canvas and a canvas requires a tool for * the user to do anything. Since the flake system is responsible for handling * tools and also to change the active tool when needed we provide one class * that can be used by an application canvas to route all the native events too * which will transparently be routed to the active tool. Without the application * having to bother about which tool is active. */ class KRITAFLAKE_EXPORT KoToolProxy : public QObject { Q_OBJECT public: /** * Constructor * @param canvas Each canvas has 1 toolProxy. Pass the parent here. * @param parent a parent QObject for memory management purposes. */ explicit KoToolProxy(KoCanvasBase *canvas, QObject *parent = 0); ~KoToolProxy() override; /// Forwarded to the current KoToolBase void paint(QPainter &painter, const KoViewConverter &converter); /// Forwarded to the current KoToolBase void repaintDecorations(); /// Forwarded to the current KoToolBase void tabletEvent(QTabletEvent *event, const QPointF &point); /// Forwarded to the current KoToolBase void mousePressEvent(QMouseEvent *event, const QPointF &point); void mousePressEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point); void mouseDoubleClickEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseMoveEvent(QMouseEvent *event, const QPointF &point); void mouseMoveEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseReleaseEvent(QMouseEvent *event, const QPointF &point); void mouseReleaseEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void keyPressEvent(QKeyEvent *event); /// Forwarded to the current KoToolBase void keyReleaseEvent(QKeyEvent *event); /// Forwarded to the current KoToolBase void explicitUserStrokeEndRequest(); /// Forwarded to the current KoToolBase QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /// Forwarded to the current KoToolBase void inputMethodEvent(QInputMethodEvent *event); /// Forwarded to the current KoToolBase QMenu* popupActionsMenu(); /// Forwarded to the current KoToolBase void deleteSelection(); /// This method gives the proxy a chance to do things. for example it is need to have working singlekey /// shortcuts. call it from the canvas' event function and forward it to QWidget::event() later. void processEvent(QEvent *) const; /// returns true if the current tool holds a selection bool hasSelection() const; /// Forwarded to the current KoToolBase void cut(); /// Forwarded to the current KoToolBase void copy() const; /// Forwarded to the current KoToolBase bool paste(); /// Forwarded to the current KoToolBase void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /// Forwarded to the current KoToolBase void dragLeaveEvent(QDragLeaveEvent *event); /// Forwarded to the current KoToolBase void dropEvent(QDropEvent *event, const QPointF &point); /// Set the new active tool. virtual void setActiveTool(KoToolBase *tool); + void touchEvent(QTouchEvent* event, const QPointF& point); + /// \internal KoToolProxyPrivate *priv(); protected Q_SLOTS: /// Forwarded to the current KoToolBase void requestUndoDuringStroke(); /// Forwarded to the current KoToolBase void requestStrokeCancellation(); /// Forwarded to the current KoToolBase void requestStrokeEnd(); Q_SIGNALS: /** * A tool can have a selection that is copy-able, this signal is emitted when that status changes. * @param hasSelection is true when the tool holds selected data. */ void selectionChanged(bool hasSelection); /** * Emitted every time a tool is changed. * @param toolId the id of the tool. * @see KoToolBase::toolId() */ void toolChanged(const QString &toolId); protected: virtual QPointF widgetToDocument(const QPointF &widgetPoint) const; KoCanvasBase* canvas() const; private: Q_PRIVATE_SLOT(d, void timeout()) Q_PRIVATE_SLOT(d, void selectionChanged(bool)) friend class KoToolProxyPrivate; KoToolProxyPrivate * const d; }; #endif // _KO_TOOL_PROXY_H_ diff --git a/libs/flake/KoTosContainer.cpp b/libs/flake/KoTosContainer.cpp index c39f684795..83f26535b1 100644 --- a/libs/flake/KoTosContainer.cpp +++ b/libs/flake/KoTosContainer.cpp @@ -1,360 +1,349 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 KO GmbH * Copyright (C) 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 "KoTosContainer.h" #include "KoTosContainer_p.h" #include "KoShapeRegistry.h" #include "KoShapeFactoryBase.h" #include "KoShapeLoadingContext.h" #include "KoTextShapeDataBase.h" #include "KoTosContainerModel.h" #include "KoStyleStack.h" #include "KoOdfLoadingContext.h" #include "KoXmlNS.h" #include "KoGenStyle.h" #include #include #include -KoTosContainerPrivate::KoTosContainerPrivate(KoShapeContainer *q) - : KoShapeContainerPrivate(q) +KoTosContainer::Private::Private() + : QSharedData() , resizeBehavior(KoTosContainer::IndependentSizes) { } -KoTosContainerPrivate::KoTosContainerPrivate(const KoTosContainerPrivate &rhs, KoShapeContainer *q) - : KoShapeContainerPrivate(rhs, q), - resizeBehavior(rhs.resizeBehavior), - preferredTextRect(rhs.preferredTextRect), - alignment(rhs.alignment) +KoTosContainer::Private::Private(const Private &rhs) + : QSharedData() + , resizeBehavior(rhs.resizeBehavior) + , preferredTextRect(rhs.preferredTextRect) + , alignment(rhs.alignment) { } -KoTosContainerPrivate::~KoTosContainerPrivate() +KoTosContainer::Private::~Private() { } KoTosContainer::KoTosContainer() - : KoShapeContainer(new KoTosContainerPrivate(this)) + : KoShapeContainer() + , d(new Private) { } -KoTosContainer::~KoTosContainer() +KoTosContainer::KoTosContainer(const KoTosContainer &rhs) + : KoShapeContainer(rhs) + , d(rhs.d) { - delete textShape(); } -KoTosContainer::KoTosContainer(KoTosContainerPrivate *dd) - : KoShapeContainer(dd) +KoTosContainer::~KoTosContainer() { + delete textShape(); } void KoTosContainer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &) { } bool KoTosContainer::loadText(const KoXmlElement &element, KoShapeLoadingContext &context) { - Q_D(const KoTosContainer); - KoXmlElement child; forEachElement(child, element) { // only recreate the text shape if there's something to be loaded if (child.localName() == "p" || child.localName() == "list") { KoShape *textShape = createTextShape(context.documentResourceManager()); if (!textShape) { return false; } //apply the style properties to the loaded text setTextAlignment(d->alignment); // In the case of text on shape, we cannot ask the text shape to load // the odf, since it expects a complete document with style info and // everything, so we have to use the KoTextShapeData object instead. KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); Q_ASSERT(shapeData); shapeData->loadStyle(element, context); bool loadOdf = shapeData->loadOdf(element, context); return loadOdf; } } return true; } void KoTosContainer::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { - Q_D(KoTosContainer); - KoShapeContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align")); Qt::Alignment vAlignment(Qt::AlignTop); if (verticalAlign == "bottom") { vAlignment = Qt::AlignBottom; } else if (verticalAlign == "justify") { // not yet supported vAlignment = Qt::AlignVCenter; } else if (verticalAlign == "middle") { vAlignment = Qt::AlignVCenter; } QString horizontalAlign(styleStack.property(KoXmlNS::draw, "textarea-horizontal-align")); Qt::Alignment hAlignment(Qt::AlignLeft); if (horizontalAlign == "center") { hAlignment = Qt::AlignCenter; } else if (horizontalAlign == "justify") { // not yet supported hAlignment = Qt::AlignCenter; } else if (horizontalAlign == "right") { hAlignment = Qt::AlignRight; } d->alignment = vAlignment | hAlignment; } QString KoTosContainer::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Qt::Alignment alignment = textAlignment(); QString verticalAlignment = "top"; Qt::Alignment vAlignment(alignment & Qt::AlignVertical_Mask); if (vAlignment == Qt::AlignBottom) { verticalAlignment = "bottom"; } else if (vAlignment == Qt::AlignVCenter || vAlignment == Qt::AlignCenter) { verticalAlignment = "middle"; } style.addProperty("draw:textarea-vertical-align", verticalAlignment); QString horizontalAlignment = "left"; Qt::Alignment hAlignment(alignment & Qt::AlignHorizontal_Mask); if (hAlignment == Qt::AlignCenter || hAlignment == Qt::AlignHCenter) { horizontalAlignment = "center"; } else if (hAlignment == Qt::AlignJustify) { horizontalAlignment = "justify"; } else if (hAlignment == Qt::AlignRight) { horizontalAlignment = "right"; } style.addProperty("draw:textarea-horizontal-align", horizontalAlignment); return KoShapeContainer::saveStyle(style, context); } void KoTosContainer::saveText(KoShapeSavingContext &context) const { KoShape *textShape = this->textShape(); if (!textShape) { return; } // In the case of text on shape, we cannot ask the text shape to save // the odf, since it would save all the frame information as well, which // is wrong. // Only save the text shape if it has content. KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); if (shapeData && !shapeData->document()->isEmpty()) { shapeData->saveOdf(context); } } void KoTosContainer::setPlainText(const QString &text) { KoShape *textShape = this->textShape(); if (textShape == 0) { warnFlake << "No text shape present in KoTosContainer"; return; } KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); Q_ASSERT(shapeData->document()); shapeData->document()->setPlainText(text); } void KoTosContainer::setResizeBehavior(ResizeBehavior resizeBehavior) { - Q_D(KoTosContainer); if (d->resizeBehavior == resizeBehavior) { return; } d->resizeBehavior = resizeBehavior; - if (d->model) { - d->model->containerChanged(this, KoShape::SizeChanged); + if (model()) { + model()->containerChanged(this, KoShape::SizeChanged); } } KoTosContainer::ResizeBehavior KoTosContainer::resizeBehavior() const { - Q_D(const KoTosContainer); return d->resizeBehavior; } void KoTosContainer::setTextAlignment(Qt::Alignment alignment) { - Q_D(KoTosContainer); - KoShape *textShape = this->textShape(); if (textShape == 0) { warnFlake << "No text shape present in KoTosContainer"; return; } // vertical KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); shapeData->setVerticalAlignment(alignment); // horizontal Q_ASSERT(shapeData->document()); QTextBlockFormat bf; bf.setAlignment(alignment & Qt::AlignHorizontal_Mask); QTextCursor cursor(shapeData->document()); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.mergeBlockFormat(bf); d->alignment = alignment; } Qt::Alignment KoTosContainer::textAlignment() const { KoShape *textShape = this->textShape(); if (textShape == 0) { warnFlake << "No text shape present in KoTosContainer"; return Qt::AlignTop; } // vertical KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); // the model makes sure it contains a shape that has a KoTextShapeDataBase set so no need to check that Qt::Alignment answer = shapeData->verticalAlignment() & Qt::AlignVertical_Mask; // horizontal Q_ASSERT(shapeData->document()); QTextCursor cursor(shapeData->document()); answer = answer | (cursor.blockFormat().alignment() & Qt::AlignHorizontal_Mask); return answer; } void KoTosContainer::setPreferredTextRect(const QRectF &rect) { - Q_D(KoTosContainer); d->preferredTextRect = rect; KoShape *textShape = this->textShape(); //debugFlake << rect << textShape << d->resizeBehavior; if (d->resizeBehavior == TextFollowsPreferredTextRect && textShape) { //debugFlake << rect; textShape->setPosition(rect.topLeft()); textShape->setSize(rect.size()); } } QRectF KoTosContainer::preferredTextRect() const { - Q_D(const KoTosContainer); return d->preferredTextRect; } KoShape *KoTosContainer::createTextShape(KoDocumentResourceManager *documentResources) { if (!documentResources) { warnFlake << "KoDocumentResourceManager not found"; return 0; } - Q_D(KoTosContainer); - delete textShape(); - delete d->model; + delete model(); - d->model = new KoTosContainerModel(); + setModel(new KoTosContainerModel()); QSet delegates; delegates << this; KoShape *textShape = 0; KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get("TextShapeID"); if (factory) { // not installed, that's too bad, but allowed textShape = factory->createDefaultShape(documentResources); Q_ASSERT(textShape); // would be a bug in the text shape; if (d->resizeBehavior == TextFollowsPreferredTextRect) { textShape->setSize(d->preferredTextRect.size()); } else { textShape->setSize(size()); } if (d->resizeBehavior == TextFollowsPreferredTextRect) { textShape->setPosition(d->preferredTextRect.topLeft()); } else { textShape->setPosition(QPointF(0, 0)); } textShape->setSelectable(false); textShape->setRunThrough(runThrough()); KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); Q_ASSERT(shapeData); // would be a bug in kotext // TODO check if that is correct depending on the resize mode shapeData->setVerticalAlignment(Qt::AlignVCenter); addShape(textShape); // textShape->setZIndex(zIndex() + 1); // not needed as there as the text shape is the only sub shape delegates << textShape; } else { warnFlake << "Text shape factory not found"; } setToolDelegates(delegates); return textShape; } KoShape *KoTosContainer::textShape() const { const QList subShapes = shapes(); return subShapes.isEmpty() ? 0 : subShapes.at(0); } void KoTosContainer::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); - Q_D(KoTosContainer); - if (d->model == 0) { + if (model() == 0) { return; } if (type == SizeChanged || type == ContentChanged) { - d->model->containerChanged(this, type); + model()->containerChanged(this, type); } // TODO is this needed? #if 0 - Q_FOREACH (KoShape *shape, d->model->shapes()) + Q_FOREACH (KoShape *shape, model()->shapes()) shape->notifyChanged(); #endif } void KoTosContainer::setRunThrough(short int runThrough) { KoShape::setRunThrough(runThrough); KoShape *textShape = this->textShape(); if (textShape) { textShape->setRunThrough(runThrough); } } diff --git a/libs/flake/KoTosContainer.h b/libs/flake/KoTosContainer.h index 011941fc3b..5a7c2fc33f 100644 --- a/libs/flake/KoTosContainer.h +++ b/libs/flake/KoTosContainer.h @@ -1,132 +1,131 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 KO GmbH * Copyright (C) 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. */ #ifndef KOTOSCONTAINER_H #define KOTOSCONTAINER_H #include "KoShapeContainer.h" #include "kritaflake_export.h" -class KoTosContainerPrivate; class KoDocumentResourceManager; /** * Container that is used to wrap a shape with a text on top. * Path shapes inherit from this class to make it possible to have text associated * with them. */ class KRITAFLAKE_EXPORT KoTosContainer : public KoShapeContainer { public: KoTosContainer(); ~KoTosContainer() override; // reimplemented void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; // reimplemented virtual bool loadText(const KoXmlElement &element, KoShapeLoadingContext &context); // reimplemented virtual void saveText(KoShapeSavingContext &context) const; /// different kinds of resizing behavior to determine how to treat text overflow enum ResizeBehavior { TextFollowsSize, ///< Text area is same size as content, extra text will be clipped FollowTextSize, ///< Content shape will get resized if text grows/shrinks IndependentSizes, ///< The text can get bigger than the content TextFollowsPreferredTextRect ///< The size/position of the text area will follow the preferredTextRect property }; /** * Set the behavior that is used to resize the text or content. * In order to determine what to do when there is too much text to fit or suddenly less * text the user can define the wanted behavior using the ResizeBehavior * @param resizeBehavior the new ResizeBehavior */ void setResizeBehavior(ResizeBehavior resizeBehavior); /** * Returns the current ResizeBehavior. */ ResizeBehavior resizeBehavior() const; /** Sets the alignment of the text. */ void setTextAlignment(Qt::Alignment alignment); /** Returns the alignment of all text */ Qt::Alignment textAlignment() const; /** * Set some plain text to be displayed on the shape. * @param text the full text. */ void setPlainText(const QString &text); /** * Add text the current shape with the specified document resource manager. * * @param documentResources * @return The created text shape or 0 in case it failed */ KoShape *createTextShape(KoDocumentResourceManager *documentResources = 0); void setRunThrough(short int runThrough) override; protected: - /// constructor - KoTosContainer(KoTosContainerPrivate *); + KoTosContainer(const KoTosContainer &rhs); //reimplemented void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override; //reimplemented QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override; /** * Set the current preferred text rectangle. This rect contains the coordinates of * the embedded text shape relative to the content shape. This value is ignored if * resizeBehavior is not TextFollowsPreferredTextRect. * @param rect the new preferred text rectangle */ void setPreferredTextRect(const QRectF &rect); /** * Returns the current preferred text rectangle. */ QRectF preferredTextRect() const; /** * Returns the text shape * * @returns textshape if set or 0 in case it is not yet set */ KoShape *textShape() const; void shapeChanged(ChangeType type, KoShape *shape = 0) override; -protected: - Q_DECLARE_PRIVATE(KoTosContainer) +private: + class Private; + QSharedDataPointer d; }; #endif diff --git a/libs/flake/KoTosContainer_p.h b/libs/flake/KoTosContainer_p.h index b794b9879c..55ca64ffae 100644 --- a/libs/flake/KoTosContainer_p.h +++ b/libs/flake/KoTosContainer_p.h @@ -1,23 +1,25 @@ #ifndef KOTOSCONTAINER_P_H #define KOTOSCONTAINER_P_H #include "kritaflake_export.h" #include "KoShapeContainer_p.h" #include "KoTosContainer.h" +#include +#include -class KRITAFLAKE_EXPORT KoTosContainerPrivate : public KoShapeContainerPrivate +class KoTosContainer::Private : public QSharedData { public: - explicit KoTosContainerPrivate(KoShapeContainer *q); - explicit KoTosContainerPrivate(const KoTosContainerPrivate &rhs, KoShapeContainer *q); + explicit Private(); + explicit Private(const Private &rhs); - ~KoTosContainerPrivate() override; + virtual ~Private(); KoTosContainer::ResizeBehavior resizeBehavior; QRectF preferredTextRect; Qt::Alignment alignment; }; #endif // KOTOSCONTAINER_P_H diff --git a/libs/flake/KoVectorPatternBackground.cpp b/libs/flake/KoVectorPatternBackground.cpp index 522a4a643b..7a295eac5e 100644 --- a/libs/flake/KoVectorPatternBackground.cpp +++ b/libs/flake/KoVectorPatternBackground.cpp @@ -1,176 +1,165 @@ /* * 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 "KoVectorPatternBackground.h" #include #include #include -#include "KoShapeBackground_p.h" #include #include -class KoVectorPatternBackgroundPrivate : public KoShapeBackgroundPrivate +class KoVectorPatternBackground::Private : public QSharedData { public: - KoVectorPatternBackgroundPrivate() + Private() + : QSharedData() { } - ~KoVectorPatternBackgroundPrivate() override + ~Private() { qDeleteAll(shapes); shapes.clear(); } QList shapes; KoFlake::CoordinateSystem referenceCoordinates = KoFlake::ObjectBoundingBox; KoFlake::CoordinateSystem contentCoordinates = KoFlake::UserSpaceOnUse; QRectF referenceRect; QTransform patternTransform; }; KoVectorPatternBackground::KoVectorPatternBackground() - : KoShapeBackground(*(new KoVectorPatternBackgroundPrivate())) + : KoShapeBackground() + , d(new Private) { } KoVectorPatternBackground::~KoVectorPatternBackground() { } bool KoVectorPatternBackground::compareTo(const KoShapeBackground *other) const { Q_UNUSED(other); return false; } void KoVectorPatternBackground::setReferenceCoordinates(KoFlake::CoordinateSystem value) { - Q_D(KoVectorPatternBackground); d->referenceCoordinates = value; } KoFlake::CoordinateSystem KoVectorPatternBackground::referenceCoordinates() const { - Q_D(const KoVectorPatternBackground); return d->referenceCoordinates; } void KoVectorPatternBackground::setContentCoordinates(KoFlake::CoordinateSystem value) { - Q_D(KoVectorPatternBackground); d->contentCoordinates = value; } KoFlake::CoordinateSystem KoVectorPatternBackground::contentCoordinates() const { - Q_D(const KoVectorPatternBackground); return d->contentCoordinates; } void KoVectorPatternBackground::setReferenceRect(const QRectF &value) { - Q_D(KoVectorPatternBackground); d->referenceRect = value; } QRectF KoVectorPatternBackground::referenceRect() const { - Q_D(const KoVectorPatternBackground); return d->referenceRect; } void KoVectorPatternBackground::setPatternTransform(const QTransform &value) { - Q_D(KoVectorPatternBackground); d->patternTransform = value; } QTransform KoVectorPatternBackground::patternTransform() const { - Q_D(const KoVectorPatternBackground); return d->patternTransform; } void KoVectorPatternBackground::setShapes(const QList value) { - Q_D(KoVectorPatternBackground); qDeleteAll(d->shapes); d->shapes.clear(); d->shapes = value; } QList KoVectorPatternBackground::shapes() const { - Q_D(const KoVectorPatternBackground); return d->shapes; } void KoVectorPatternBackground::paint(QPainter &painter, const KoViewConverter &converter_Unused, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const { Q_UNUSED(context_Unused); Q_UNUSED(converter_Unused); - Q_D(const KoVectorPatternBackground); - const QPainterPath dstShapeOutline = fillPath; const QRectF dstShapeBoundingBox = dstShapeOutline.boundingRect(); KoBakedShapeRenderer renderer(dstShapeOutline, QTransform(), QTransform(), d->referenceRect, d->contentCoordinates != KoFlake::UserSpaceOnUse, dstShapeBoundingBox, d->referenceCoordinates != KoFlake::UserSpaceOnUse, d->patternTransform); QPainter *patchPainter = renderer.bakeShapePainter(); KoViewConverter converter; KoShapePainter p; p.setShapes(d->shapes); p.paint(*patchPainter, converter); // uncomment for debug // renderer.patchImage().save("dd_patch_image.png"); painter.setPen(Qt::NoPen); renderer.renderShape(painter); } bool KoVectorPatternBackground::hasTransparency() const { return true; } void KoVectorPatternBackground::fillStyle(KoGenStyle &, KoShapeSavingContext &) { // noop } bool KoVectorPatternBackground::loadStyle(KoOdfLoadingContext &, const QSizeF &Size) { Q_UNUSED(Size); return true; } diff --git a/libs/flake/KoVectorPatternBackground.h b/libs/flake/KoVectorPatternBackground.h index a8d5625fec..1abdbc28f7 100644 --- a/libs/flake/KoVectorPatternBackground.h +++ b/libs/flake/KoVectorPatternBackground.h @@ -1,68 +1,67 @@ /* * 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. */ #ifndef KOVECTORPATTERNBACKGROUND_H #define KOVECTORPATTERNBACKGROUND_H #include #include +#include class KoShape; class QPointF; class QRectF; class QTransform; -class KoVectorPatternBackgroundPrivate; class KoVectorPatternBackground : public KoShapeBackground { public: KoVectorPatternBackground(); ~KoVectorPatternBackground() override; bool compareTo(const KoShapeBackground *other) const override; void setReferenceCoordinates(KoFlake::CoordinateSystem value); KoFlake::CoordinateSystem referenceCoordinates() const; /** * In ViewBox just use the same mode as for referenceCoordinates */ void setContentCoordinates(KoFlake::CoordinateSystem value); KoFlake::CoordinateSystem contentCoordinates() const; void setReferenceRect(const QRectF &value); QRectF referenceRect() const; void setPatternTransform(const QTransform &value); QTransform patternTransform() const; void setShapes(const QList value); QList shapes() const; void paint(QPainter &painter, const KoViewConverter &converter_Unused, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const override; bool hasTransparency() const override; void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; - private: - Q_DECLARE_PRIVATE(KoVectorPatternBackground) - Q_DISABLE_COPY(KoVectorPatternBackground) + class Private; + QSharedDataPointer d; }; #endif // KOVECTORPATTERNBACKGROUND_H diff --git a/libs/flake/resources/KoGamutMask.cpp b/libs/flake/resources/KoGamutMask.cpp index d7da355b0d..10ce479e85 100644 --- a/libs/flake/resources/KoGamutMask.cpp +++ b/libs/flake/resources/KoGamutMask.cpp @@ -1,428 +1,427 @@ /* * Copyright (c) 2018 Anna Medonosova * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoGamutMask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - KoGamutMaskShape::KoGamutMaskShape(KoShape* shape) : m_maskShape(shape) , m_shapePaintingContext(KoShapePaintingContext()) { } KoGamutMaskShape::KoGamutMaskShape() {}; KoGamutMaskShape::~KoGamutMaskShape() {}; KoShape* KoGamutMaskShape::koShape() { return m_maskShape; } bool KoGamutMaskShape::coordIsClear(const QPointF& coord, const KoViewConverter& viewConverter, int maskRotation) const { // apply mask rotation to coord const KisGamutMaskViewConverter& converter = dynamic_cast(viewConverter); QPointF centerPoint(converter.viewSize().width()*0.5, converter.viewSize().height()*0.5); QTransform rotationTransform; rotationTransform.translate(centerPoint.x(), centerPoint.y()); rotationTransform.rotate(-maskRotation); rotationTransform.translate(-centerPoint.x(), -centerPoint.y()); QPointF rotatedCoord = rotationTransform.map(coord); QPointF translatedPoint = viewConverter.viewToDocument(rotatedCoord); bool isClear = m_maskShape->hitTest(translatedPoint); return isClear; } void KoGamutMaskShape::paint(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation) { painter.save(); // apply mask rotation before drawing QPointF centerPoint(painter.viewport().width()*0.5, painter.viewport().height()*0.5); painter.translate(centerPoint); painter.rotate(maskRotation); painter.translate(-centerPoint); painter.setTransform(m_maskShape->absoluteTransformation(&viewConverter) * painter.transform()); m_maskShape->paint(painter, viewConverter, m_shapePaintingContext); painter.restore(); } void KoGamutMaskShape::paintStroke(QPainter &painter, const KoViewConverter &viewConverter, int maskRotation) { painter.save(); // apply mask rotation before drawing QPointF centerPoint(painter.viewport().width()*0.5, painter.viewport().height()*0.5); painter.translate(centerPoint); painter.rotate(maskRotation); painter.translate(-centerPoint); painter.setTransform(m_maskShape->absoluteTransformation(&viewConverter) * painter.transform()); m_maskShape->paintStroke(painter, viewConverter, m_shapePaintingContext); painter.restore(); } struct Q_DECL_HIDDEN KoGamutMask::Private { QString name; QString title; QString description; QByteArray data; QVector maskShapes; QVector previewShapes; QSizeF maskSize; int rotation; }; KoGamutMask::KoGamutMask(const QString& filename) : KoResource(filename) , d(new Private()) { d->maskSize = QSizeF(144.0,144.0); setRotation(0); } KoGamutMask::KoGamutMask() : KoResource(QString()) , d(new Private()) { d->maskSize = QSizeF(144.0,144.0); setRotation(0); } KoGamutMask::KoGamutMask(KoGamutMask* rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs->filename()); setTitle(rhs->title()); setDescription(rhs->description()); d->maskSize = rhs->d->maskSize; QList newShapes; for(KoShape* sh: rhs->koShapes()) { newShapes.append(sh->cloneShape()); } setMaskShapes(newShapes); setValid(true); } bool KoGamutMask::coordIsClear(const QPointF& coord, KoViewConverter &viewConverter, bool preview) { QVector* shapeVector; if (preview && !d->previewShapes.isEmpty()) { shapeVector = &d->previewShapes; } else { shapeVector = &d->maskShapes; } for(KoGamutMaskShape* shape: *shapeVector) { if (shape->coordIsClear(coord, viewConverter, rotation()) == true) { return true; } } return false; } void KoGamutMask::paint(QPainter &painter, KoViewConverter& viewConverter, bool preview) { QVector* shapeVector; if (preview && !d->previewShapes.isEmpty()) { shapeVector = &d->previewShapes; } else { shapeVector = &d->maskShapes; } for(KoGamutMaskShape* shape: *shapeVector) { shape->paint(painter, viewConverter, rotation()); } } void KoGamutMask::paintStroke(QPainter &painter, KoViewConverter &viewConverter, bool preview) { QVector* shapeVector; if (preview && !d->previewShapes.isEmpty()) { shapeVector = &d->previewShapes; } else { shapeVector = &d->maskShapes; } for(KoGamutMaskShape* shape: *shapeVector) { shape->paintStroke(painter, viewConverter, rotation()); } } bool KoGamutMask::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnFlake << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoGamutMask::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); // TODO: test KIS_ASSERT_RECOVER_RETURN_VALUE(d->data.size() != 0, false); if (filename().isNull()) { warnFlake << "Cannot load gamut mask" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnFlake << "Cannot load gamut mask" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-gamutmask", KoStore::Zip)); if (!store || store->bad()) return false; bool storeOpened = store->open("gamutmask.svg"); if (!storeOpened) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QString errorMsg; int errorLine = 0; int errorColumn = 0; KoXmlDocument xmlDocument = SvgParser::createDocumentFromSvg(ba, &errorMsg, &errorLine, &errorColumn); if (xmlDocument.isNull()) { errorFlake << "Parsing error in " << filename() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errorFlake << "Parsing error in the main document at line" << errorLine << ", column" << errorColumn << endl << "Error message: " << errorMsg; return false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; QList shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize); d->maskSize = fragmentSize; d->title = parser.documentTitle(); setName(d->title); d->description = parser.documentDescription(); setMaskShapes(shapes); if (store->open("preview.png")) { KoStoreDevice previewDev(store.data()); previewDev.open(QIODevice::ReadOnly); QImage preview = QImage(); preview.load(&previewDev, "PNG"); setImage(preview); (void)store->close(); } buf.close(); setValid(true); return true; } void KoGamutMask::setMaskShapes(QList shapes) { setMaskShapesToVector(shapes, d->maskShapes); } bool KoGamutMask::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } QList KoGamutMask::koShapes() const { QList shapes; for(KoGamutMaskShape* maskShape: d->maskShapes) { shapes.append(maskShape->koShape()); } return shapes; } bool KoGamutMask::saveToDevice(QIODevice *dev) const { KoStore* store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-gamutmask", KoStore::Zip)); if (!store || store->bad()) return false; QList shapes = koShapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); if (!store->open("gamutmask.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); SvgWriter writer(shapes); writer.setDocumentTitle(d->title); writer.setDocumentDescription(d->description); writer.save(storeDev, d->maskSize); if (!store->close()) { return false; } if (!store->open("preview.png")) { return false; } KoStoreDevice previewDev(store); previewDev.open(QIODevice::WriteOnly); image().save(&previewDev, "PNG"); if (!store->close()) { return false; } return store->finalize(); } QString KoGamutMask::title() { return d->title; } void KoGamutMask::setTitle(QString title) { d->title = title; setName(title); } QString KoGamutMask::description() { return d->description; } void KoGamutMask::setDescription(QString description) { d->description = description; } int KoGamutMask::rotation() { return d->rotation; } void KoGamutMask::setRotation(int rotation) { d->rotation = rotation; } QSizeF KoGamutMask::maskSize() { return d->maskSize; } void KoGamutMask::setPreviewMaskShapes(QList shapes) { setMaskShapesToVector(shapes, d->previewShapes); } void KoGamutMask::setMaskShapesToVector(QList shapes, QVector &targetVector) { targetVector.clear(); for(KoShape* sh: shapes) { KoGamutMaskShape* maskShape = new KoGamutMaskShape(sh); targetVector.append(maskShape); } } // clean up when ending mask preview void KoGamutMask::clearPreview() { d->previewShapes.clear(); } diff --git a/libs/flake/resources/tests/CMakeLists.txt b/libs/flake/resources/tests/CMakeLists.txt new file mode 100644 index 0000000000..30f5bd1963 --- /dev/null +++ b/libs/flake/resources/tests/CMakeLists.txt @@ -0,0 +1,14 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +include(ECMAddTests) + +include_directories(${CMAKE_SOURCE_DIR}/sdk/tests) + +macro_add_unittest_definitions() + +# needs kritaimage for TestUtil +ecm_add_tests( + KoGamutMaskTest.cpp +# KoGamutMaskShapeTest.cpp + LINK_LIBRARIES kritaflake kritaimage Qt5::Test + NAME_PREFIX "libs-flake-") diff --git a/libs/flake/resources/tests/KoGamutMaskTest.cpp b/libs/flake/resources/tests/KoGamutMaskTest.cpp new file mode 100644 index 0000000000..a7763c74e7 --- /dev/null +++ b/libs/flake/resources/tests/KoGamutMaskTest.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2019 Anna Medonosova + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include +#include +#include + +#include + +#include "KoGamutMaskTest.h" + +KoGamutMaskTest::KoGamutMaskTest(QObject *parent) : QObject(parent) +{ + +} + +void KoGamutMaskTest::testCoordIsClear() +{ + QFETCH(QString, maskFile); + QFETCH(QPointF, coord); + QFETCH(int, maskRotation); + QFETCH(bool, expectedOutput); + + QScopedPointer mask(new KoGamutMask(TestUtil::fetchDataFileLazy(maskFile))); + mask->load(); + Q_ASSERT(mask->valid()); + mask->setRotation(maskRotation); + + KisGamutMaskViewConverter* converter = new KisGamutMaskViewConverter(); + converter->setMaskSize(mask->maskSize()); + converter->setViewSize(QSize(100.0, 100.0)); + + bool maskOutput = mask->coordIsClear(coord, *converter, false); + QCOMPARE(maskOutput, expectedOutput); +} + +void KoGamutMaskTest::testCoordIsClear_data() +{ + QTest::addColumn("maskFile"); + QTest::addColumn("coord"); + QTest::addColumn("maskRotation"); + QTest::addColumn("expectedOutput"); + + // single shape mask + QTest::addRow("Atmospheric_Triad.kgm: disallowed coordinate, no rotation") << "Atmospheric_Triad.kgm" + << QPointF(0.0, 0.0) << 0 + << false; + + QTest::addRow("Atmospheric_Triad.kgm: allowed coordinate, no rotation") << "Atmospheric_Triad.kgm" + << QPointF(33.0, 71.0) << 0 + << true; + + QTest::addRow("Atmospheric_Triad.kgm: disallowed coordinate, with rotation") << "Atmospheric_Triad.kgm" + << QPointF(33.0, 71.0) << 180 + << false; + + QTest::addRow("Atmospheric_Triad.kgm: allowed coordinate, with rotation") << "Atmospheric_Triad.kgm" + << QPointF(76.4,60.9) << 180 + << true; + + + // multiple shapes mask + QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 1, no rotation") + << "Dominant_Hue_With_Accent.kgm" + << QPointF(71.0, 49.0) << 0 + << true; + + QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 2, no rotation") + << "Dominant_Hue_With_Accent.kgm" + << QPointF(11.0, 51.0) << 0 + << true; + + QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 1, with rotation") + << "Dominant_Hue_With_Accent.kgm" + << QPointF(40.0, 21.0) << 256 + << true; + + QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 2, with rotation") + << "Dominant_Hue_With_Accent.kgm" + << QPointF(57.0, 82.0) << 256 + << true; +} + +void KoGamutMaskTest::testLoad() +{ + QFETCH(QString, maskFile); + QFETCH(QString, expectedTitle); + QFETCH(QString, expectedDescription); + QFETCH(int, expectedShapeCount); + + QScopedPointer mask(new KoGamutMask(TestUtil::fetchDataFileLazy(maskFile))); + mask->load(); + + Q_ASSERT(mask->valid()); + + QCOMPARE(mask->title(), expectedTitle); + QCOMPARE(mask->description(), expectedDescription); + QCOMPARE(mask->koShapes().size(), expectedShapeCount); +} + +void KoGamutMaskTest::testLoad_data() +{ + QTest::addColumn("maskFile"); + QTest::addColumn("expectedTitle"); + QTest::addColumn("expectedDescription"); + QTest::addColumn("expectedShapeCount"); + + QTest::addRow("single shape mask") + << "Atmospheric_Triad.kgm" + << "Atmospheric Triad" << "test gamut mask description" + << 1; + + QTest::addRow("multiple shape mask") + << "Dominant_Hue_With_Accent.kgm" + << "Dominant Hue With Accent" << "" + << 2; +} + +QTEST_MAIN(KoGamutMaskTest); diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/libs/flake/resources/tests/KoGamutMaskTest.h similarity index 64% copy from plugins/impex/ppm/tests/kis_ppm_test.h copy to libs/flake/resources/tests/KoGamutMaskTest.h index 827856e793..0af7d63331 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.h +++ b/libs/flake/resources/tests/KoGamutMaskTest.h @@ -1,35 +1,39 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (c) 2019 Anna Medonosova * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_PPM_TEST_H_ -#define _KIS_PPM_TEST_H_ +#ifndef KOGAMUTMASKTEST_H +#define KOGAMUTMASKTEST_H -#include +#include -class KisPPMTest : public QObject +class KoGamutMaskTest : public QObject { Q_OBJECT +public: + explicit KoGamutMaskTest(QObject *parent = nullptr); + private Q_SLOTS: - void testFiles(); + void testCoordIsClear(); + void testCoordIsClear_data(); - void testImportFromWriteonly(); - void testExportToReadonly(); - void testImportIncorrectFormat(); + void testLoad(); + void testLoad_data(); +// TODO: add preview vs. non-preview testing }; -#endif +#endif // KOGAMUTMASKTEST_H diff --git a/libs/flake/resources/tests/data/Atmospheric_Triad.kgm b/libs/flake/resources/tests/data/Atmospheric_Triad.kgm new file mode 100644 index 0000000000..40f30cf3b4 Binary files /dev/null and b/libs/flake/resources/tests/data/Atmospheric_Triad.kgm differ diff --git a/libs/flake/resources/tests/data/Dominant_Hue_With_Accent.kgm b/libs/flake/resources/tests/data/Dominant_Hue_With_Accent.kgm new file mode 100644 index 0000000000..1bfecf468e Binary files /dev/null and b/libs/flake/resources/tests/data/Dominant_Hue_With_Accent.kgm differ diff --git a/libs/flake/svg/SvgSavingContext.cpp b/libs/flake/svg/SvgSavingContext.cpp index 36a798d658..299a9ccb59 100644 --- a/libs/flake/svg/SvgSavingContext.cpp +++ b/libs/flake/svg/SvgSavingContext.cpp @@ -1,256 +1,256 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgSavingContext.h" #include "SvgUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN SvgSavingContext::Private { public: Private(QIODevice *_mainDevice, QIODevice *_styleDevice) : mainDevice(_mainDevice) , styleDevice(_styleDevice) , styleWriter(0) , shapeWriter(0) , saveInlineImages(true) { styleWriter.reset(new KoXmlWriter(&styleBuffer, 1)); styleWriter->startElement("defs"); shapeWriter.reset(new KoXmlWriter(&shapeBuffer, 1)); const qreal scaleToUserSpace = SvgUtil::toUserSpace(1.0); userSpaceMatrix.scale(scaleToUserSpace, scaleToUserSpace); } ~Private() { } QIODevice *mainDevice; QIODevice *styleDevice; QBuffer styleBuffer; QBuffer shapeBuffer; QScopedPointer styleWriter; QScopedPointer shapeWriter; QHash uniqueNames; QHash shapeIds; QTransform userSpaceMatrix; bool saveInlineImages; bool strippedTextMode = false; }; SvgSavingContext::SvgSavingContext(QIODevice &outputDevice, bool saveInlineImages) : d(new Private(&outputDevice, 0)) { d->saveInlineImages = saveInlineImages; } SvgSavingContext::SvgSavingContext(QIODevice &shapesDevice, QIODevice &styleDevice, bool saveInlineImages) : d(new Private(&shapesDevice, &styleDevice)) { d->saveInlineImages = saveInlineImages; } SvgSavingContext::~SvgSavingContext() { d->styleWriter->endElement(); if (d->styleDevice) { d->styleDevice->write(d->styleBuffer.data()); } else { d->mainDevice->write(d->styleBuffer.data()); d->mainDevice->write("\n"); } d->mainDevice->write(d->shapeBuffer.data()); delete d; } KoXmlWriter &SvgSavingContext::styleWriter() { return *d->styleWriter; } KoXmlWriter &SvgSavingContext::shapeWriter() { return *d->shapeWriter; } QString SvgSavingContext::createUID(const QString &base) { QString idBase = base.isEmpty() ? "defitem" : base; int counter = d->uniqueNames.value(idBase); d->uniqueNames.insert(idBase, counter+1); return idBase + QString("%1").arg(counter); } QString SvgSavingContext::getID(const KoShape *obj) { QString id; // do we have already an id for this object ? if (d->shapeIds.contains(obj)) { // use existing id id = d->shapeIds[obj]; } else { // initialize from object name id = obj->name(); // if object name is not empty and was not used already // we can use it as is if (!id.isEmpty() && !d->uniqueNames.contains(id)) { // add to unique names so it does not get reused d->uniqueNames.insert(id, 1); } else { if (id.isEmpty()) { // differentiate a little between shape types if (dynamic_cast(obj)) id = "group"; else if (dynamic_cast(obj)) id = "layer"; else id = "shape"; } // create a completely new id based on object name // or a generic name id = createUID(id); } // record id for this shape d->shapeIds.insert(obj, id); } return id; } QTransform SvgSavingContext::userSpaceTransform() const { return d->userSpaceMatrix; } bool SvgSavingContext::isSavingInlineImages() const { return d->saveInlineImages; } QString SvgSavingContext::createFileName(const QString &extension) { QFile *file = qobject_cast(d->mainDevice); if (!file) return QString(); QFileInfo fi(file->fileName()); QString path = fi.absolutePath(); - QString dstBaseFilename = fi.baseName(); + QString dstBaseFilename = fi.completeBaseName(); // create a filename for the image file at the destination directory QString fname = dstBaseFilename + '_' + createUID("file"); // check if file exists already int i = 0; QString counter; // change filename as long as the filename already exists while (QFile(path + fname + counter + extension).exists()) { counter = QString("_%1").arg(++i); } return fname + counter + extension; } QString SvgSavingContext::saveImage(const QImage &image) { if (isSavingInlineImages()) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (image.save(&buffer, "PNG")) { const QString header("data:image/x-png;base64,"); return header + ba.toBase64(); } } else { // write to a temp file first QTemporaryFile imgFile; if (image.save(&imgFile, "PNG")) { QString dstFilename = createFileName(".png"); if (QFile::copy(imgFile.fileName(), dstFilename)) { return dstFilename; } else { QFile f(imgFile.fileName()); f.remove(); } } } return QString(); } QString SvgSavingContext::saveImage(KoImageData *image) { if (isSavingInlineImages()) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (image->saveData(buffer)) { const QString header("data:" + KisMimeDatabase::mimeTypeForSuffix(image->suffix()) + ";base64,"); return header + ba.toBase64(); } } else { // write to a temp file first QTemporaryFile imgFile; if (image->saveData(imgFile)) { QString ext = image->suffix(); QString dstFilename = createFileName(ext); // move the temp file to the destination directory if (QFile::copy(imgFile.fileName(), dstFilename)) { return dstFilename; } else { QFile f(imgFile.fileName()); f.remove(); } } } return QString(); } void SvgSavingContext::setStrippedTextMode(bool value) { d->strippedTextMode = value; } bool SvgSavingContext::strippedTextMode() const { return d->strippedTextMode; } diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt index f2a07a9033..905eed5224 100644 --- a/libs/flake/tests/CMakeLists.txt +++ b/libs/flake/tests/CMakeLists.txt @@ -1,89 +1,90 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests( TestPosition.cpp TestSelection.cpp TestPathTool.cpp TestShapeAt.cpp TestShapePainting.cpp TestKoShapeFactory.cpp TestKoShapeRegistry.cpp TestShapeContainer.cpp TestShapeGroupCommand.cpp TestShapeReorderCommand.cpp TestImageCollection.cpp TestResourceManager.cpp TestShapeBackgroundCommand.cpp TestShapeStrokeCommand.cpp TestShapeShadowCommand.cpp TestInputDevice.cpp TestSnapStrategy.cpp TestPathShape.cpp TestControlPointMoveCommand.cpp TestPointTypeCommand.cpp TestPointRemoveCommand.cpp TestRemoveSubpathCommand.cpp TestPathSegment.cpp TestSegmentTypeCommand.cpp TestKoDrag.cpp TestKoMarkerCollection.cpp + KisGamutMaskViewConverterTest.cpp LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") ecm_add_test( TestSvgParser.cpp TEST_NAME TestSvgParser LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") ecm_add_test( TestSvgParser.cpp TEST_NAME TestSvgParserCloned LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgParserCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) ecm_add_test( TestSvgParser.cpp TEST_NAME TestSvgParserRoundTrip LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgParserRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) ############## broken tests ############### krita_add_broken_unit_test(TestPointMergeCommand.cpp TEST_NAME TestPointMergeCommand LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") krita_add_broken_unit_test( TestSvgText.cpp TEST_NAME TestSvgText LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") krita_add_broken_unit_test( TestSvgText.cpp TEST_NAME TestSvgTextCloned LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgTextCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) krita_add_broken_unit_test( TestSvgText.cpp TEST_NAME TestSvgTextRoundTrip LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgTextRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) diff --git a/libs/flake/tests/KisGamutMaskViewConverterTest.cpp b/libs/flake/tests/KisGamutMaskViewConverterTest.cpp new file mode 100644 index 0000000000..e3cd26ebf9 --- /dev/null +++ b/libs/flake/tests/KisGamutMaskViewConverterTest.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019 Anna Medonosova + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include + +#include "KisGamutMaskViewConverterTest.h" + + + +KisGamutMaskViewConverterTest::KisGamutMaskViewConverterTest(QObject *parent) : QObject(parent) +{ + +} + +void KisGamutMaskViewConverterTest::testDocumentToViewX() +{ + QFETCH(qreal, input); + QFETCH(qreal, expectedOutput); + QFETCH(QSizeF, maskSize); + QFETCH(QSize, viewSize); + + QScopedPointer converter(new KisGamutMaskViewConverter()); + converter->setMaskSize(maskSize); + converter->setViewSize(viewSize); + + qreal converterOutput = converter->documentToViewX(input); + QCOMPARE(converterOutput, expectedOutput); +} + +void KisGamutMaskViewConverterTest::testDocumentToViewX_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedOutput"); + QTest::addColumn("maskSize"); + QTest::addColumn("viewSize"); + + QTest::newRow("mask < view") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 400); + QTest::newRow("view < mask") << 2.0 << 1.0 << QSizeF(400.0, 400.0) << QSize(200, 200); + + QTest::newRow("view not square") << 2.0 << 4.0 << QSizeF(200.0, 300.0) << QSize(400, 400); + QTest::newRow("mask not square") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 600); +} + +void KisGamutMaskViewConverterTest::testDocumentToViewY() +{ + QFETCH(qreal, input); + QFETCH(qreal, expectedOutput); + QFETCH(QSizeF, maskSize); + QFETCH(QSize, viewSize); + + QScopedPointer converter(new KisGamutMaskViewConverter()); + converter->setMaskSize(maskSize); + converter->setViewSize(viewSize); + + qreal converterOutput = converter->documentToViewY(input); + QCOMPARE(converterOutput, expectedOutput); +} + +void KisGamutMaskViewConverterTest::testDocumentToViewY_data() +{ + + QTest::addColumn("input"); + QTest::addColumn("expectedOutput"); + QTest::addColumn("maskSize"); + QTest::addColumn("viewSize"); + + QTest::newRow("mask < view") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 400); + QTest::newRow("view < mask") << 2.0 << 1.0 << QSizeF(400.0, 400.0) << QSize(200, 200); + + QTest::newRow("view not square") << 2.0 << 4.0 << QSizeF(200.0, 300.0) << QSize(400, 400); + QTest::newRow("mask not square") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 600); +} + +void KisGamutMaskViewConverterTest::testViewToDocumentX() +{ + QFETCH(qreal, input); + QFETCH(qreal, expectedOutput); + QFETCH(QSizeF, maskSize); + QFETCH(QSize, viewSize); + + QScopedPointer converter(new KisGamutMaskViewConverter()); + converter->setMaskSize(maskSize); + converter->setViewSize(viewSize); + + qreal converterOutput = converter->viewToDocumentX(input); + QCOMPARE(converterOutput, expectedOutput); +} + +void KisGamutMaskViewConverterTest::testViewToDocumentX_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedOutput"); + QTest::addColumn("maskSize"); + QTest::addColumn("viewSize"); + + QTest::newRow("mask < view") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 400); + QTest::newRow("view < mask") << 1.0 << 2.0 << QSizeF(400.0, 400.0) << QSize(200, 200); + + QTest::newRow("view not square") << 4.0 << 2.0 << QSizeF(200.0, 300.0) << QSize(400, 400); + QTest::newRow("mask not square") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 600); +} + +void KisGamutMaskViewConverterTest::testViewToDocumentY() +{ + QFETCH(qreal, input); + QFETCH(qreal, expectedOutput); + QFETCH(QSizeF, maskSize); + QFETCH(QSize, viewSize); + + QScopedPointer converter(new KisGamutMaskViewConverter()); + converter->setMaskSize(maskSize); + converter->setViewSize(viewSize); + + qreal converterOutput = converter->viewToDocumentY(input); + QCOMPARE(converterOutput, expectedOutput); +} + +void KisGamutMaskViewConverterTest::testViewToDocumentY_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedOutput"); + QTest::addColumn("maskSize"); + QTest::addColumn("viewSize"); + + + QTest::newRow("mask < view") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 400); + QTest::newRow("view < mask") << 1.0 << 2.0 << QSizeF(400.0, 400.0) << QSize(200, 200); + + QTest::newRow("view not square") << 4.0 << 2.0 << QSizeF(200.0, 300.0) << QSize(400, 400); + QTest::newRow("mask not square") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 600); +} + +QTEST_MAIN(KisGamutMaskViewConverterTest); diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/libs/flake/tests/KisGamutMaskViewConverterTest.h similarity index 55% copy from plugins/impex/ppm/tests/kis_ppm_test.h copy to libs/flake/tests/KisGamutMaskViewConverterTest.h index 827856e793..fdde941568 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.h +++ b/libs/flake/tests/KisGamutMaskViewConverterTest.h @@ -1,35 +1,45 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (c) 2019 Anna Medonosova * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_PPM_TEST_H_ -#define _KIS_PPM_TEST_H_ +#ifndef KISGAMUTMASKVIEWCONVERTERTEST_H +#define KISGAMUTMASKVIEWCONVERTERTEST_H -#include +#include +#include -class KisPPMTest : public QObject +class KisGamutMaskViewConverterTest : public QObject { Q_OBJECT +public: + explicit KisGamutMaskViewConverterTest(QObject *parent = nullptr); + private Q_SLOTS: - void testFiles(); + void testDocumentToViewX(); + void testDocumentToViewX_data(); + + void testDocumentToViewY(); + void testDocumentToViewY_data(); + + void testViewToDocumentX(); + void testViewToDocumentX_data(); - void testImportFromWriteonly(); - void testExportToReadonly(); - void testImportIncorrectFormat(); + void testViewToDocumentY(); + void testViewToDocumentY_data(); }; -#endif +#endif // KISGAMUTMASKVIEWCONVERTERTEST_H diff --git a/libs/flake/text/KoSvgTextChunkShape.cpp b/libs/flake/text/KoSvgTextChunkShape.cpp index e456c76495..15122653de 100644 --- a/libs/flake/text/KoSvgTextChunkShape.cpp +++ b/libs/flake/text/KoSvgTextChunkShape.cpp @@ -1,1013 +1,985 @@ /* * Copyright (c) 2017 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. +v * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoSvgTextChunkShape.h" #include "KoSvgTextChunkShape_p.h" #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include "kis_debug.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include namespace { void appendLazy(QVector *list, boost::optional value, int iteration, bool hasDefault = true, qreal defaultValue = 0.0) { if (!value) return; if (value && *value == defaultValue && hasDefault == true && list->isEmpty()) return; while (list->size() < iteration) { list->append(defaultValue); } list->append(*value); } void fillTransforms(QVector *xPos, QVector *yPos, QVector *dxPos, QVector *dyPos, QVector *rotate, QVector localTransformations) { for (int i = 0; i < localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = localTransformations[i]; appendLazy(xPos, t.xPos, i, false); appendLazy(yPos, t.yPos, i, false); appendLazy(dxPos, t.dxPos, i); appendLazy(dyPos, t.dyPos, i); appendLazy(rotate, t.rotate, i); } } QVector parseListAttributeX(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitX(context.currentGC(), str); } return result; } QVector parseListAttributeY(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitY(context.currentGC(), str); } return result; } QVector parseListAttributeAngular(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitAngular(context.currentGC(), str); } return result; } QString convertListAttribute(const QVector &values) { QStringList stringValues; Q_FOREACH (qreal value, values) { stringValues.append(KisDomUtils::toString(value)); } return stringValues.join(','); } } -struct KoSvgTextChunkShapePrivate::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface +struct KoSvgTextChunkShape::Private::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface { LayoutInterface(KoSvgTextChunkShape *_q) : q(_q) {} KoSvgText::AutoValue textLength() const override { - return q->d_func()->textLength; + return q->d->textLength; } KoSvgText::LengthAdjust lengthAdjust() const override { - return q->d_func()->lengthAdjust; + return q->d->lengthAdjust; } int numChars() const override { - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d_func()->text.isEmpty(), 0); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0); int result = 0; if (!q->shapeCount()) { - result = q->d_func()->text.size(); + result = q->d->text.size(); } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); result += chunkShape->layoutInterface()->numChars(); } } return result; } int relativeCharPos(KoSvgTextChunkShape *child, int pos) const override { QList childShapes = q->shapes(); int result = -1; int numCharsPassed = 0; Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); if (chunkShape == child) { result = pos + numCharsPassed; break; } else { numCharsPassed += chunkShape->layoutInterface()->numChars(); } } return result; } bool isTextNode() const override { - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d_func()->text.isEmpty(), false); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), false); return !q->shapeCount(); } QString nodeText() const override { - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d_func()->text.isEmpty(), 0); - return !q->shapeCount() ? q->d_func()->text : QString(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0); + return !q->shapeCount() ? q->d->text : QString(); } QVector localCharTransformations() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(isTextNode(), QVector()); - const QVector t = q->d_func()->localTransformations; - return t.mid(0, qMin(t.size(), q->d_func()->text.size())); + const QVector t = q->d->localTransformations; + return t.mid(0, qMin(t.size(), q->d->text.size())); } static QString getBidiOpening(KoSvgText::Direction direction, KoSvgText::UnicodeBidi bidi) { using namespace KoSvgText; QString result; if (bidi == BidiEmbed) { result = direction == DirectionLeftToRight ? "\u202a" : "\u202b"; } else if (bidi == BidiOverride) { result = direction == DirectionLeftToRight ? "\u202d" : "\u202e"; } return result; } QVector collectSubChunks() const override { QVector result; if (isTextNode()) { - const QString text = q->d_func()->text; - const KoSvgText::KoSvgCharChunkFormat format = q->d_func()->fetchCharFormat(); - QVector transforms = q->d_func()->localTransformations; + const QString text = q->d->text; + const KoSvgText::KoSvgCharChunkFormat format = q->fetchCharFormat(); + QVector transforms = q->d->localTransformations; /** * Sometimes SVG can contain the X,Y offsets for the pieces of text that * do not exist, just skip them. */ if (text.size() <= transforms.size()) { transforms.resize(text.size()); } - KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->d_func()->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt()); - KoSvgText::Direction direction = KoSvgText::Direction(q->d_func()->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); + KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->d->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt()); + KoSvgText::Direction direction = KoSvgText::Direction(q->d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); const QString bidiOpening = getBidiOpening(direction, bidi); if (!bidiOpening.isEmpty()) { result << SubChunk(bidiOpening, format); } if (transforms.isEmpty()) { result << SubChunk(text, format); } else { for (int i = 0; i < transforms.size(); i++) { const KoSvgText::CharTransformation baseTransform = transforms[i]; int subChunkLength = 1; for (int j = i + 1; j < transforms.size(); j++) { if (transforms[j].isNull()) { subChunkLength++; } else { break; } } if (i + subChunkLength >= transforms.size()) { subChunkLength = text.size() - i; } result << SubChunk(text.mid(i, subChunkLength), format, baseTransform); i += subChunkLength - 1; } } if (!bidiOpening.isEmpty()) { result << SubChunk("\u202c", format); } } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result += chunkShape->layoutInterface()->collectSubChunks(); } } return result; } void addAssociatedOutline(const QRectF &rect) override { KIS_SAFE_ASSERT_RECOVER_RETURN(isTextNode()); QPainterPath path; path.addRect(rect); - path |= q->d_func()->associatedOutline; + path |= q->d->associatedOutline; path.setFillRule(Qt::WindingFill); path = path.simplified(); - q->d_func()->associatedOutline = path; - q->d_func()->size = path.boundingRect().size(); + q->d->associatedOutline = path; + q->setSize(path.boundingRect().size()); q->notifyChanged(); - q->d_func()->shapeChanged(KoShape::SizeChanged); + q->shapeChangedPriv(KoShape::SizeChanged); } void clearAssociatedOutline() override { - q->d_func()->associatedOutline = QPainterPath(); - q->d_func()->size = QSizeF(); + q->d->associatedOutline = QPainterPath(); + q->setSize(QSizeF()); q->notifyChanged(); - q->d_func()->shapeChanged(KoShape::SizeChanged); + q->shapeChangedPriv(KoShape::SizeChanged); } private: KoSvgTextChunkShape *q; }; KoSvgTextChunkShape::KoSvgTextChunkShape() - : KoShapeContainer(new KoSvgTextChunkShapePrivate(this)) + : KoShapeContainer() + , d(new Private) { - Q_D(KoSvgTextChunkShape); - d->layoutInterface.reset(new KoSvgTextChunkShapePrivate::LayoutInterface(this)); + d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs) - : KoShapeContainer(new KoSvgTextChunkShapePrivate(*rhs.d_func(), this)) + : KoShapeContainer(rhs) + , d(rhs.d) { - Q_D(KoSvgTextChunkShape); - d->layoutInterface.reset(new KoSvgTextChunkShapePrivate::LayoutInterface(this)); -} - -KoSvgTextChunkShape::KoSvgTextChunkShape(KoSvgTextChunkShapePrivate *dd) - : KoShapeContainer(dd) -{ - Q_D(KoSvgTextChunkShape); - d->layoutInterface.reset(new KoSvgTextChunkShapePrivate::LayoutInterface(this)); + if (rhs.model()) { + SimpleShapeContainerModel *otherModel = dynamic_cast(rhs.model()); + KIS_ASSERT_RECOVER_RETURN(otherModel); + setModelInit(new SimpleShapeContainerModel(*otherModel)); + } + // XXX: this will immediately lead to a detach + d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::~KoSvgTextChunkShape() { } KoShape *KoSvgTextChunkShape::cloneShape() const { return new KoSvgTextChunkShape(*this); } QSizeF KoSvgTextChunkShape::size() const { return outlineRect().size(); } void KoSvgTextChunkShape::setSize(const QSizeF &size) { Q_UNUSED(size); // we do not support resizing! } QRectF KoSvgTextChunkShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoSvgTextChunkShape::outline() const { - Q_D(const KoSvgTextChunkShape); - QPainterPath result; result.setFillRule(Qt::WindingFill); if (d->layoutInterface->isTextNode()) { result = d->associatedOutline; } else { Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result |= chunkShape->outline(); } } return result.simplified(); } void KoSvgTextChunkShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintContext); } void KoSvgTextChunkShape::saveOdf(KoShapeSavingContext &context) const { Q_UNUSED(context); } bool KoSvgTextChunkShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(element); Q_UNUSED(context); return false; } bool KoSvgTextChunkShape::saveHtml(HtmlSavingContext &context) { - Q_D(KoSvgTextChunkShape); - // Should we add a newline? Check for vertical movement if we're using rtl or ltr text // XXX: if vertical text, check horizontal movement. QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, d->localTransformations); for (int i = 0; i < d->localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = d->localTransformations[i]; appendLazy(&xPos, t.xPos, i, false); appendLazy(&yPos, t.yPos, i, false); appendLazy(&dxPos, t.dxPos, i); appendLazy(&dyPos, t.dyPos, i); } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); // XXX: we don't save fill, stroke, text length, length adjust or spacing and glyphs. KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); if (isRootTextNode()) { context.shapeWriter().startElement("body", false); if (layoutInterface()->isTextNode()) { context.shapeWriter().startElement("p", false); } // XXX: Save the style? } else if (parent->isRootTextNode()) { context.shapeWriter().startElement("p", false); } else { context.shapeWriter().startElement("span", false); // XXX: Save the style? } QMap attributes = ownProperties.convertToSvgTextAttributes(); if (attributes.size() > 0) { QString styleString; for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { if (QString(it.key().toLatin1().data()).contains("text-anchor")) { QString val = it.value(); if (it.value()=="middle") { val = "center"; } else if (it.value()=="end") { val = "right"; } else { val = "left"; } styleString.append("text-align") .append(": ") .append(val) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("fill")){ styleString.append("color") .append(": ") .append(it.value()) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("font-size")){ QString val = it.value(); if (QRegExp ("\\d*").exactMatch(val)) { val.append("pt"); } styleString.append(it.key().toLatin1().data()) .append(": ") .append(val) .append(";" ); } else { styleString.append(it.key().toLatin1().data()) .append(": ") .append(it.value()) .append(";" ); } } context.shapeWriter().addAttribute("style", styleString); } if (layoutInterface()->isTextNode()) { debugFlake << "saveHTML" << this << d->text << xPos << yPos << dxPos << dyPos; // After adding all the styling to the

element, add the text context.shapeWriter().addTextNode(d->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveHtml(context); } } if (isRootTextNode() && layoutInterface()->isTextNode()) { context.shapeWriter().endElement(); // body } context.shapeWriter().endElement(); // p or span return true; } void writeTextListAttribute(const QString &attribute, const QVector &values, KoXmlWriter &writer) { const QString value = convertListAttribute(values); if (!value.isEmpty()) { writer.addAttribute(attribute.toLatin1().data(), value); } } bool KoSvgTextChunkShape::saveSvg(SvgSavingContext &context) { - Q_D(KoSvgTextChunkShape); - if (isRootTextNode()) { context.shapeWriter().startElement("text", false); if (!context.strippedTextMode()) { context.shapeWriter().addAttribute("id", context.getID(this)); context.shapeWriter().addAttribute("krita:useRichText", d->isRichTextPreferred ? "true" : "false"); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); } else { SvgStyleWriter::saveSvgFill(this, context); SvgStyleWriter::saveSvgStroke(this, context); } } else { context.shapeWriter().startElement("tspan", false); if (!context.strippedTextMode()) { SvgStyleWriter::saveSvgBasicStyle(this, context); } } if (layoutInterface()->isTextNode()) { QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, d->localTransformations); writeTextListAttribute("x", xPos, context.shapeWriter()); writeTextListAttribute("y", yPos, context.shapeWriter()); writeTextListAttribute("dx", dxPos, context.shapeWriter()); writeTextListAttribute("dy", dyPos, context.shapeWriter()); writeTextListAttribute("rotate", rotate, context.shapeWriter()); } if (!d->textLength.isAuto) { context.shapeWriter().addAttribute("textLength", KisDomUtils::toString(d->textLength.customValue)); if (d->lengthAdjust == KoSvgText::LengthAdjustSpacingAndGlyphs) { context.shapeWriter().addAttribute("lengthAdjust", "spacingAndGlyphs"); } } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); // we write down stroke/fill iff they are different from the parent's value if (!isRootTextNode()) { if (ownProperties.hasProperty(KoSvgTextProperties::FillId)) { SvgStyleWriter::saveSvgFill(this, context); } if (ownProperties.hasProperty(KoSvgTextProperties::StrokeId)) { SvgStyleWriter::saveSvgStroke(this, context); } } QMap attributes = ownProperties.convertToSvgTextAttributes(); for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { context.shapeWriter().addAttribute(it.key().toLatin1().data(), it.value()); } if (layoutInterface()->isTextNode()) { context.shapeWriter().addTextNode(d->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveSvg(context); } } context.shapeWriter().endElement(); return true; } -void KoSvgTextChunkShapePrivate::loadContextBasedProperties(SvgGraphicsContext *gc) +void KoSvgTextChunkShape::Private::loadContextBasedProperties(SvgGraphicsContext *gc) { properties = gc->textProperties; font = gc->font; fontFamiliesList = gc->fontFamiliesList; } void KoSvgTextChunkShape::resetTextShape() { - Q_D(KoSvgTextChunkShape); - using namespace KoSvgText; d->properties = KoSvgTextProperties(); d->font = QFont(); d->fontFamiliesList = QStringList(); d->textLength = AutoValue(); d->lengthAdjust = LengthAdjustSpacing; d->localTransformations.clear(); d->text.clear(); // all the subchunks are destroyed! // (first detach, then destroy) QList shapesToReset = shapes(); Q_FOREACH (KoShape *shape, shapesToReset) { shape->setParent(0); delete shape; } } bool KoSvgTextChunkShape::loadSvg(const KoXmlElement &e, SvgLoadingContext &context) { - Q_D(KoSvgTextChunkShape); - SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); d->loadContextBasedProperties(gc); d->textLength = KoSvgText::parseAutoValueXY(e.attribute("textLength", ""), context, ""); d->lengthAdjust = KoSvgText::parseLengthAdjust(e.attribute("lengthAdjust", "spacing")); QVector xPos = parseListAttributeX(e.attribute("x", ""), context); QVector yPos = parseListAttributeY(e.attribute("y", ""), context); QVector dxPos = parseListAttributeX(e.attribute("dx", ""), context); QVector dyPos = parseListAttributeY(e.attribute("dy", ""), context); QVector rotate = parseListAttributeAngular(e.attribute("rotate", ""), context); const int numLocalTransformations = std::max({xPos.size(), yPos.size(), dxPos.size(), dyPos.size(), rotate.size()}); d->localTransformations.resize(numLocalTransformations); for (int i = 0; i < numLocalTransformations; i++) { if (i < xPos.size()) { d->localTransformations[i].xPos = xPos[i]; } if (i < yPos.size()) { d->localTransformations[i].yPos = yPos[i]; } if (i < dxPos.size() && dxPos[i] != 0.0) { d->localTransformations[i].dxPos = dxPos[i]; } if (i < dyPos.size() && dyPos[i] != 0.0) { d->localTransformations[i].dyPos = dyPos[i]; } if (i < rotate.size()) { d->localTransformations[i].rotate = rotate[i]; } } return true; } namespace { QString cleanUpString(QString text) { text.replace(QRegExp("[\\r\\n]"), ""); text.replace(QRegExp(" {2,}"), " "); return text; } enum Result { FoundNothing, FoundText, FoundSpace }; Result hasPreviousSibling(KoXmlNode node) { while (!node.isNull()) { if (node.isElement()) { KoXmlElement element = node.toElement(); if (element.tagName() == "text") break; } while (!node.previousSibling().isNull()) { node = node.previousSibling(); while (!node.lastChild().isNull()) { node = node.lastChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); if (!text.isEmpty()) { // if we are the leading whitespace, we should report that // we are the last if (text == " ") { return hasPreviousSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } return text[text.size() - 1] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } Result hasNextSibling(KoXmlNode node) { while (!node.isNull()) { while (!node.nextSibling().isNull()) { node = node.nextSibling(); while (!node.firstChild().isNull()) { node = node.firstChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); // if we are the trailing whitespace, we should report that // we are the last if (text == " ") { return hasNextSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } if (!text.isEmpty()) { return text[0] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } } bool KoSvgTextChunkShape::loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context) { - Q_D(KoSvgTextChunkShape); - SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); d->loadContextBasedProperties(gc); QString data = cleanUpString(text.data()); const Result leftBorder = hasPreviousSibling(text); const Result rightBorder = hasNextSibling(text); if (data.startsWith(' ') && leftBorder == FoundNothing) { data.remove(0, 1); } if (data.endsWith(' ') && rightBorder != FoundText) { data.remove(data.size() - 1, 1); } if (data == " " && (leftBorder == FoundNothing || rightBorder == FoundNothing)) { data = ""; } //ENTER_FUNCTION() << text.data() << "-->" << data; d->text = data; return !data.isEmpty(); } void KoSvgTextChunkShape::normalizeCharTransformations() { - Q_D(KoSvgTextChunkShape); - d->applyParentCharTransformations(d->localTransformations); + applyParentCharTransformations(d->localTransformations); } void KoSvgTextChunkShape::simplifyFillStrokeInheritance() { if (!isRootTextNode()) { KoShape *parentShape = parent(); KIS_SAFE_ASSERT_RECOVER_RETURN(parentShape); QSharedPointer bg = background(); QSharedPointer parentBg = parentShape->background(); if (!inheritBackground() && ((!bg && !parentBg) || (bg && parentBg && bg->compareTo(parentShape->background().data())))) { setInheritBackground(true); } KoShapeStrokeModelSP stroke = this->stroke(); KoShapeStrokeModelSP parentStroke= parentShape->stroke(); if (!inheritStroke() && ((!stroke && !parentStroke) || (stroke && parentStroke && stroke->compareFillTo(parentShape->stroke().data()) && stroke->compareStyleTo(parentShape->stroke().data())))) { setInheritStroke(true); } } Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->simplifyFillStrokeInheritance(); } } KoSvgTextProperties KoSvgTextChunkShape::textProperties() const { - Q_D(const KoSvgTextChunkShape); - KoSvgTextProperties properties = d->properties; properties.setProperty(KoSvgTextProperties::FillId, QVariant::fromValue(KoSvgText::BackgroundProperty(background()))); properties.setProperty(KoSvgTextProperties::StrokeId, QVariant::fromValue(KoSvgText::StrokeProperty(stroke()))); return properties; } bool KoSvgTextChunkShape::isTextNode() const { - Q_D(const KoSvgTextChunkShape); return d->layoutInterface->isTextNode(); } KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface() { - Q_D(KoSvgTextChunkShape); return d->layoutInterface.data(); } bool KoSvgTextChunkShape::isRichTextPreferred() const { - Q_D(const KoSvgTextChunkShape); return isRootTextNode() && d->isRichTextPreferred; } void KoSvgTextChunkShape::setRichTextPreferred(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRootTextNode()); - Q_D(KoSvgTextChunkShape); d->isRichTextPreferred = value; } bool KoSvgTextChunkShape::isRootTextNode() const { return false; } /**************************************************************************************************/ -/* KoSvgTextChunkShapePrivate */ +/* KoSvgTextChunkShape::Private */ /**************************************************************************************************/ #include "SimpleShapeContainerModel.h" -KoSvgTextChunkShapePrivate::KoSvgTextChunkShapePrivate(KoSvgTextChunkShape *_q) - : KoShapeContainerPrivate(_q) +KoSvgTextChunkShape::Private::Private() + : QSharedData() { } -KoSvgTextChunkShapePrivate::KoSvgTextChunkShapePrivate(const KoSvgTextChunkShapePrivate &rhs, KoSvgTextChunkShape *q) - : KoShapeContainerPrivate(rhs, q), - properties(rhs.properties), - font(rhs.font), - fontFamiliesList(rhs.fontFamiliesList), - localTransformations(rhs.localTransformations), - textLength(rhs.textLength), - lengthAdjust(rhs.lengthAdjust), - text(rhs.text), - isRichTextPreferred(rhs.isRichTextPreferred) +KoSvgTextChunkShape::Private::Private(const Private &rhs) + : QSharedData() + , properties(rhs.properties) + , font(rhs.font) + , fontFamiliesList(rhs.fontFamiliesList) + , localTransformations(rhs.localTransformations) + , textLength(rhs.textLength) + , lengthAdjust(rhs.lengthAdjust) + , text(rhs.text) + , isRichTextPreferred(rhs.isRichTextPreferred) { - if (rhs.model) { - SimpleShapeContainerModel *otherModel = dynamic_cast(rhs.model); - KIS_ASSERT_RECOVER_RETURN(otherModel); - model = new SimpleShapeContainerModel(*otherModel); - } } -KoSvgTextChunkShapePrivate::~KoSvgTextChunkShapePrivate() +KoSvgTextChunkShape::Private::~Private() { } #include #include #include -KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShapePrivate::fetchCharFormat() const +KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShape::fetchCharFormat() const { - Q_Q(const KoSvgTextChunkShape); - KoSvgText::KoSvgCharChunkFormat format; - format.setFont(font); - format.setTextAnchor(KoSvgText::TextAnchor(properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt())); + format.setFont(d->font); + format.setTextAnchor(KoSvgText::TextAnchor(d->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt())); KoSvgText::Direction direction = - KoSvgText::Direction(properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); + KoSvgText::Direction(d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); format.setLayoutDirection(direction == KoSvgText::DirectionLeftToRight ? Qt::LeftToRight : Qt::RightToLeft); KoSvgText::BaselineShiftMode shiftMode = - KoSvgText::BaselineShiftMode(properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt()); + KoSvgText::BaselineShiftMode(d->properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt()); // FIXME: we support only 'none', 'sub' and 'super' shifts at the moment. // Please implement 'percentage' as well! // WARNING!!! Qt's setVerticalAlignment() also changes the size of the font! And SVG does not(!) imply it! if (shiftMode == KoSvgText::ShiftSub) { format.setVerticalAlignment(QTextCharFormat::AlignSubScript); } else if (shiftMode == KoSvgText::ShiftSuper) { format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); } - KoSvgText::AutoValue letterSpacing = properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value(); + KoSvgText::AutoValue letterSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value(); if (!letterSpacing.isAuto) { format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing.customValue); } - KoSvgText::AutoValue wordSpacing = properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value(); + KoSvgText::AutoValue wordSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value(); if (!wordSpacing.isAuto) { format.setFontWordSpacing(wordSpacing.customValue); } - KoSvgText::AutoValue kerning = properties.propertyOrDefault(KoSvgTextProperties::KerningId).value(); + KoSvgText::AutoValue kerning = d->properties.propertyOrDefault(KoSvgTextProperties::KerningId).value(); if (!kerning.isAuto) { format.setFontKerning(false); format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(format.fontLetterSpacing() + kerning.customValue); } QBrush textBrush = Qt::NoBrush; - if (q->background()) { - KoColorBackground *colorBackground = dynamic_cast(q->background().data()); + if (background()) { + KoColorBackground *colorBackground = dynamic_cast(background().data()); if (!colorBackground) { qWarning() << "TODO: support gradient and pattern backgrounds for text"; textBrush = Qt::red; } if (colorBackground) { textBrush = colorBackground->brush(); } } format.setForeground(textBrush); QPen textPen = Qt::NoPen; - if (q->stroke()) { - KoShapeStroke *stroke = dynamic_cast(q->stroke().data()); + if (stroke()) { + KoShapeStroke *stroke = dynamic_cast(this->stroke().data()); if (stroke) { textPen = stroke->resultLinePen(); } } format.setTextOutline(textPen); // TODO: avoid const_cast somehow... - format.setAssociatedShape(const_cast(q)); + format.setAssociatedShape(const_cast(this)); return format; } -void KoSvgTextChunkShapePrivate::applyParentCharTransformations(const QVector transformations) +void KoSvgTextChunkShape::applyParentCharTransformations(const QVector transformations) { - Q_Q(KoSvgTextChunkShape); - - if (q->shapeCount()) { + if (shapeCount()) { int numCharsPassed = 0; - Q_FOREACH (KoShape *shape, q->shapes()) { + Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); const int numCharsInSubtree = chunkShape->layoutInterface()->numChars(); QVector t = transformations.mid(numCharsPassed, numCharsInSubtree); if (t.isEmpty()) break; - chunkShape->d_func()->applyParentCharTransformations(t); + chunkShape->applyParentCharTransformations(t); numCharsPassed += numCharsInSubtree; if (numCharsPassed >= transformations.size()) break; } } else { - for (int i = 0; i < qMin(transformations.size(), text.size()); i++) { - KIS_SAFE_ASSERT_RECOVER_RETURN(localTransformations.size() >= i); + for (int i = 0; i < qMin(transformations.size(), d->text.size()); i++) { + KIS_SAFE_ASSERT_RECOVER_RETURN(d->localTransformations.size() >= i); - if (localTransformations.size() == i) { - localTransformations.append(transformations[i]); + if (d->localTransformations.size() == i) { + d->localTransformations.append(transformations[i]); } else { - localTransformations[i].mergeInParentTransformation(transformations[i]); + d->localTransformations[i].mergeInParentTransformation(transformations[i]); } } } } diff --git a/libs/flake/text/KoSvgTextChunkShape.h b/libs/flake/text/KoSvgTextChunkShape.h index df6c966f72..cd310b9d48 100644 --- a/libs/flake/text/KoSvgTextChunkShape.h +++ b/libs/flake/text/KoSvgTextChunkShape.h @@ -1,174 +1,181 @@ /* * Copyright (c) 2017 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 KOSVGTEXTCHUNKSHAPE_H #define KOSVGTEXTCHUNKSHAPE_H #include "kritaflake_export.h" #include #include +#include class HtmlSavingContext; class KoSvgTextProperties; class KoSvgTextChunkShapePrivate; class KoSvgTextChunkShapeLayoutInterface; /** * KoSvgTextChunkShape is an elementary block of SVG text object. * * KoSvgTextChunkShape represents either a \ or \ element of SVG. * The chunk shape uses flake hierarchy to represent the DOM hierarchy of the * supplied text. All the attributes of text blocks can be fetched using * textProperties() method. * * KoSvgTextChunkShape uses special text properties object to overcome the * flake's "property inheritance" limitation. Basically, flake doesn't support * the inheritance of shape properties: every shape stores all the properties * that were defined at the stage of loading/creation. KoSvgTextProperties is a * wrapper that allows the user to compare the properties of the two shapes and * return only the ones that are unique for a child shape. That allows us to * generate a correct SVG/markup code that can be edited by the user easily. * * WARNING: beware the difference between "svg-text-chunk" and * KoSvgTextChunkShape! The chunk shape is **not** a "text chunk" in SVG's * definition. According to SVG, "text chunk" is a set of characters anchored to * a specific absolute position on canvas. And KoSvgTextChunkShape is just one * \ or \ element. Obviously, one \ can contain multiple "text * chunks" and, vice versa, a "text chunk" can spread onto multiple \'s. */ class KRITAFLAKE_EXPORT KoSvgTextChunkShape : public KoShapeContainer, public SvgShape { public: KoSvgTextChunkShape(); KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs); ~KoSvgTextChunkShape() override; KoShape* cloneShape() const override; QSizeF size() const override; void setSize(const QSizeF &size) override; QRectF outlineRect() const override; QPainterPath outline() const override; void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override; void saveOdf(KoShapeSavingContext &Context) const override; bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &Context) override; bool saveHtml(HtmlSavingContext &context); /** * Reset the text shape into initial state, removing all the child shapes. * This method is used by text-updating code to upload the updated text * into shape. The uploading code first calls resetTextShape() and then adds * new children. */ virtual void resetTextShape(); bool saveSvg(SvgSavingContext &context) override; bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override; bool loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context); /** * Normalize the SVG character transformations * * In SVG x,y,dx,dy,rotate attributes are inherited from the parent shapes * in a curious ways. We do not want to unwind the links on the fly, so we * just parse and adjust them right when the loading completed. After * normalizeCharTransformations() is finished, all the child shapes will * have local transformations set according to the parent's lists. */ void normalizeCharTransformations(); /** * Compress the inheritance of 'fill' and 'stroke'. * * The loading code makes no difference if the fill or stroke was inherited * or not. That is not a problem for normal shapes, but it cannot work for * text shapes. The text has different inheritance rules: if the parent * text chunk has a gradient fill, its inheriting descendants will * **smoothly continue** this gradient. They will not start a new gradient * in their local coordinate system. * * Therefore, after loading, the loading code calls * simplifyFillStrokeInheritance() which marks the coinciding strokes and * fills so that the rendering code will be able to distinguish them in the * future. * * Proper fill inheritance is also needed for the GUI. When the user * changes the color of the parent text chunk, all the inheriting children * should update its color automatically, without GUI recursively * traversing the shapes. * */ void simplifyFillStrokeInheritance(); /** * SVG properties of the text chunk * @return the properties object with fill and stroke included as a property */ KoSvgTextProperties textProperties() const; /** * Return the type of the chunk. * * The chunk can be either a "text chunk", that contains a text string * itself, of an "intermediate chunk" that doesn't contain any text itself, * but works as a group for a set of child chunks, which might be either * text (leaf) or intermediate chunks. Such groups are needed to define a * common text style for a group of '\' objects. * * @return true if the chunk is a "text chunk" false if it is "intermediate chunk" */ bool isTextNode() const; /** * A special interface for KoSvgTextShape's layout code. Don't use it * unless you are KoSvgTextShape. */ KoSvgTextChunkShapeLayoutInterface* layoutInterface(); /** * WARNING: this propperty is available only if isRootTextNode() is true * * @return true if the shape should be edited in a rich-text editor */ bool isRichTextPreferred() const; /** * WARNING: this propperty is available only if isRootTextNode() is true * * Sets whether the shape should be edited in rich-text editor */ void setRichTextPreferred(bool value); protected: /** * Show if the shape is a root of the text hierarchy. Always true for * KoSvgTextShape and always false for KoSvgTextChunkShape */ virtual bool isRootTextNode() const; protected: KoSvgTextChunkShape(KoSvgTextChunkShapePrivate *dd); private: - Q_DECLARE_PRIVATE(KoSvgTextChunkShape) + KoSvgText::KoSvgCharChunkFormat fetchCharFormat() const; + + void applyParentCharTransformations(const QVector transformations); + +private: + class Private; + QSharedDataPointer d; }; #endif // KOSVGTEXTCHUNKSHAPE_H diff --git a/libs/flake/text/KoSvgTextChunkShape_p.h b/libs/flake/text/KoSvgTextChunkShape_p.h index 8c59d969ca..602a932336 100644 --- a/libs/flake/text/KoSvgTextChunkShape_p.h +++ b/libs/flake/text/KoSvgTextChunkShape_p.h @@ -1,60 +1,55 @@ /* * Copyright (c) 2017 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 "KoSvgTextChunkShape.h" #include "KoSvgText.h" #include "KoSvgTextProperties.h" -#include +#include #include class SvgGraphicsContext; -class KoSvgTextChunkShapePrivate : public KoShapeContainerPrivate +class KoSvgTextChunkShape::Private : public QSharedData { public: - KoSvgTextChunkShapePrivate(KoSvgTextChunkShape *_q); - KoSvgTextChunkShapePrivate(const KoSvgTextChunkShapePrivate &rhs, KoSvgTextChunkShape *q); - ~KoSvgTextChunkShapePrivate(); + Private(); + Private(const Private &rhs); + ~Private(); KoSvgTextProperties properties; QFont font; QStringList fontFamiliesList; QVector localTransformations; KoSvgText::AutoValue textLength; KoSvgText::LengthAdjust lengthAdjust = KoSvgText::LengthAdjustSpacing; QString text; struct LayoutInterface; QScopedPointer layoutInterface; QPainterPath associatedOutline; - KoSvgText::KoSvgCharChunkFormat fetchCharFormat() const; - - void applyParentCharTransformations(const QVector transformations); void loadContextBasedProperties(SvgGraphicsContext *gc); bool isRichTextPreferred = true; - - Q_DECLARE_PUBLIC(KoSvgTextChunkShape) }; diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp index 6ad42a467a..6f0e81ef18 100644 --- a/libs/flake/text/KoSvgTextShape.cpp +++ b/libs/flake/text/KoSvgTextShape.cpp @@ -1,634 +1,634 @@ /* * Copyright (c) 2017 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 "KoSvgTextShape.h" #include #include #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -class KoSvgTextShapePrivate : public KoSvgTextChunkShapePrivate +#include + +class KoSvgTextShape::Private : public QSharedData { - KoSvgTextShapePrivate(KoSvgTextShape *_q) - : KoSvgTextChunkShapePrivate(_q) +public: + Private() + : QSharedData() { } - KoSvgTextShapePrivate(const KoSvgTextShapePrivate &rhs, KoSvgTextShape *q) - : KoSvgTextChunkShapePrivate(rhs, q) + Private(const Private &) + : QSharedData() { } std::vector> cachedLayouts; std::vector cachedLayoutsOffsets; QThread *cachedLayoutsWorkingThread = 0; void clearAssociatedOutlines(KoShape *rootShape); - - Q_DECLARE_PUBLIC(KoSvgTextShape) }; KoSvgTextShape::KoSvgTextShape() - : KoSvgTextChunkShape(new KoSvgTextShapePrivate(this)) + : KoSvgTextChunkShape() + , d(new Private) { setShapeId(KoSvgTextShape_SHAPEID); } KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs) - : KoSvgTextChunkShape(new KoSvgTextShapePrivate(*rhs.d_func(), this)) + : KoSvgTextChunkShape(rhs) + , d(rhs.d) { setShapeId(KoSvgTextShape_SHAPEID); // QTextLayout has no copy-ctor, so just relayout everything! relayout(); } KoSvgTextShape::~KoSvgTextShape() { } KoShape *KoSvgTextShape::cloneShape() const { return new KoSvgTextShape(*this); } void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape) { KoSvgTextChunkShape::shapeChanged(type, shape); if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) { relayout(); } } void KoSvgTextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { - Q_D(KoSvgTextShape); Q_UNUSED(paintContext); /** * HACK ALERT: * QTextLayout should only be accessed from the thread it has been created in. * If the cached layout has been created in a different thread, we should just * recreate the layouts in the current thread to be able to render them. */ if (QThread::currentThread() != d->cachedLayoutsWorkingThread) { relayout(); } applyConversion(painter, converter); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { d->cachedLayouts[i]->draw(&painter, d->cachedLayoutsOffsets[i]); } /** * HACK ALERT: * The layouts of non-gui threads must be destroyed in the same thread * they have been created. Because the thread might be restarted in the * meantime or just destroyed, meaning that the per-thread freetype data * will not be available. */ if (QThread::currentThread() != qApp->thread()) { d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = 0; } } void KoSvgTextShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintContext); // do nothing! everything is painted in paintComponent() } QPainterPath KoSvgTextShape::textOutline() { - Q_D(KoSvgTextShape); QPainterPath result; result.setFillRule(Qt::WindingFill); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; const QTextLayout *layout = d->cachedLayouts[i].get(); for (int j = 0; j < layout->lineCount(); j++) { QTextLine line = layout->lineAt(j); Q_FOREACH (const QGlyphRun &run, line.glyphRuns()) { const QVector indexes = run.glyphIndexes(); const QVector positions = run.positions(); const QRawFont font = run.rawFont(); KIS_SAFE_ASSERT_RECOVER(indexes.size() == positions.size()) { continue; } for (int k = 0; k < indexes.size(); k++) { QPainterPath glyph = font.pathForGlyph(indexes[k]); glyph.translate(positions[k] + layoutOffset); result += glyph; } const qreal thickness = font.lineThickness(); const QRectF runBounds = run.boundingRect(); if (run.overline()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y(); QRectF overlineBlob(runBounds.x(), y, runBounds.width(), thickness); overlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(overlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.strikeOut()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y() + 0.5 * line.height(); QRectF strikeThroughBlob(runBounds.x(), y, runBounds.width(), thickness); strikeThroughBlob.translate(layoutOffset); QPainterPath path; path.addRect(strikeThroughBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.underline()) { const qreal y = line.y() + line.ascent() + font.underlinePosition(); QRectF underlineBlob(runBounds.x(), y, runBounds.width(), thickness); underlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(underlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } } } } return result; } void KoSvgTextShape::resetTextShape() { KoSvgTextChunkShape::resetTextShape(); relayout(); } struct TextChunk { QString text; QVector formats; Qt::LayoutDirection direction = Qt::LeftToRight; Qt::Alignment alignment = Qt::AlignLeading; struct SubChunkOffset { QPointF offset; int start = 0; }; QVector offsets; boost::optional xStartPos; boost::optional yStartPos; QPointF applyStartPosOverride(const QPointF &pos) const { QPointF result = pos; if (xStartPos) { result.rx() = *xStartPos; } if (yStartPos) { result.ry() = *yStartPos; } return result; } }; QVector mergeIntoChunks(const QVector &subChunks) { QVector chunks; for (auto it = subChunks.begin(); it != subChunks.end(); ++it) { if (it->transformation.startsNewChunk() || it == subChunks.begin()) { TextChunk newChunk = TextChunk(); newChunk.direction = it->format.layoutDirection(); newChunk.alignment = it->format.calculateAlignment(); newChunk.xStartPos = it->transformation.xPos; newChunk.yStartPos = it->transformation.yPos; chunks.append(newChunk); } TextChunk ¤tChunk = chunks.last(); if (it->transformation.hasRelativeOffset()) { TextChunk::SubChunkOffset o; o.start = currentChunk.text.size(); o.offset = it->transformation.relativeOffset(); KIS_SAFE_ASSERT_RECOVER_NOOP(!o.offset.isNull()); currentChunk.offsets.append(o); } QTextLayout::FormatRange formatRange; formatRange.start = currentChunk.text.size(); formatRange.length = it->text.size(); formatRange.format = it->format; currentChunk.formats.append(formatRange); currentChunk.text += it->text; } return chunks; } /** * Qt's QTextLayout has a weird trait, it doesn't count space characters as * distinct characters in QTextLayout::setNumColumns(), that is, if we want to * position a block of text that starts with a space character in a specific * position, QTextLayout will drop this space and will move the text to the left. * * That is why we have a special wrapper object that ensures that no spaces are * dropped and their horizontal advance parameter is taken into account. */ struct LayoutChunkWrapper { LayoutChunkWrapper(QTextLayout *layout) : m_layout(layout) { } QPointF addTextChunk(int startPos, int length, const QPointF &textChunkStartPos) { QPointF currentTextPos = textChunkStartPos; const int lastPos = startPos + length - 1; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(startPos == m_addedChars, currentTextPos); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastPos < m_layout->text().size(), currentTextPos); QTextLine line; std::swap(line, m_danglingLine); if (!line.isValid()) { line = m_layout->createLine(); } // skip all the space characters that were not included into the Qt's text line const int currentLineStart = line.isValid() ? line.textStart() : startPos + length; while (startPos < currentLineStart && startPos <= lastPos) { currentTextPos.rx() += skipSpaceCharacter(startPos); startPos++; } if (startPos <= lastPos) { // defines the number of columns to look for glyphs const int numChars = lastPos - startPos + 1; // Tabs break the normal column flow // grow to avoid missing glyphs int charOffset = 0; while (line.textLength() < numChars) { line.setNumColumns(numChars + charOffset); charOffset++; } line.setPosition(currentTextPos - QPointF(0, line.ascent())); currentTextPos.rx() += line.horizontalAdvance(); // skip all the space characters that were not included into the Qt's text line for (int i = line.textStart() + line.textLength(); i < lastPos; i++) { currentTextPos.rx() += skipSpaceCharacter(i); } } else { // keep the created but unused line for future use std::swap(line, m_danglingLine); } m_addedChars += length; return currentTextPos; } private: qreal skipSpaceCharacter(int pos) { const QTextCharFormat format = formatForPos(pos, m_layout->formats()); const QChar skippedChar = m_layout->text()[pos]; KIS_SAFE_ASSERT_RECOVER_NOOP(skippedChar.isSpace() || !skippedChar.isPrint()); QFontMetrics metrics(format.font()); return metrics.width(skippedChar); } static QTextCharFormat formatForPos(int pos, const QVector &formats) { Q_FOREACH (const QTextLayout::FormatRange &range, formats) { if (pos >= range.start && pos < range.start + range.length) { return range.format; } } KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "pos should be within the bounds of the layouted text"); return QTextCharFormat(); } private: int m_addedChars = 0; QTextLayout *m_layout; QTextLine m_danglingLine; }; void KoSvgTextShape::relayout() { - Q_D(KoSvgTextShape); d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = QThread::currentThread(); QPointF currentTextPos; QVector textChunks = mergeIntoChunks(layoutInterface()->collectSubChunks()); Q_FOREACH (const TextChunk &chunk, textChunks) { std::unique_ptr layout(new QTextLayout()); QTextOption option; // WARNING: never activate this option! It breaks the RTL text layout! //option.setFlags(QTextOption::ShowTabsAndSpaces); option.setWrapMode(QTextOption::WrapAnywhere); option.setUseDesignMetrics(true); // TODO: investigate if it is needed? option.setTextDirection(chunk.direction); layout->setText(chunk.text); layout->setTextOption(option); layout->setFormats(chunk.formats); layout->setCacheEnabled(true); layout->beginLayout(); currentTextPos = chunk.applyStartPosOverride(currentTextPos); const QPointF anchorPointPos = currentTextPos; int lastSubChunkStart = 0; QPointF lastSubChunkOffset; LayoutChunkWrapper wrapper(layout.get()); for (int i = 0; i <= chunk.offsets.size(); i++) { const bool isFinalPass = i == chunk.offsets.size(); const int length = !isFinalPass ? chunk.offsets[i].start - lastSubChunkStart : chunk.text.size() - lastSubChunkStart; if (length > 0) { currentTextPos += lastSubChunkOffset; currentTextPos = wrapper.addTextChunk(lastSubChunkStart, length, currentTextPos); } if (!isFinalPass) { lastSubChunkOffset = chunk.offsets[i].offset; lastSubChunkStart = chunk.offsets[i].start; } } layout->endLayout(); QPointF diff; if (chunk.alignment & Qt::AlignTrailing || chunk.alignment & Qt::AlignHCenter) { if (chunk.alignment & Qt::AlignTrailing) { diff = currentTextPos - anchorPointPos; } else if (chunk.alignment & Qt::AlignHCenter) { diff = 0.5 * (currentTextPos - anchorPointPos); } // TODO: fix after t2b text implemented diff.ry() = 0; } d->cachedLayouts.push_back(std::move(layout)); d->cachedLayoutsOffsets.push_back(-diff); } d->clearAssociatedOutlines(this); for (int i = 0; i < int(d->cachedLayouts.size()); i++) { const QTextLayout &layout = *d->cachedLayouts[i]; const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; using namespace KoSvgText; Q_FOREACH (const QTextLayout::FormatRange &range, layout.formats()) { const KoSvgCharChunkFormat &format = static_cast(range.format); AssociatedShapeWrapper wrapper = format.associatedShapeWrapper(); const int rangeStart = range.start; const int safeRangeLength = range.length > 0 ? range.length : layout.text().size() - rangeStart; if (safeRangeLength <= 0) continue; const int rangeEnd = range.start + safeRangeLength - 1; const int firstLineIndex = layout.lineForTextPosition(rangeStart).lineNumber(); const int lastLineIndex = layout.lineForTextPosition(rangeEnd).lineNumber(); for (int i = firstLineIndex; i <= lastLineIndex; i++) { const QTextLine line = layout.lineAt(i); // It might happen that the range contains only one (or two) // symbol that is a whitespace symbol. In such a case we should // just skip this (invalid) line. if (!line.isValid()) continue; const int posStart = qMax(line.textStart(), rangeStart); const int posEnd = qMin(line.textStart() + line.textLength() - 1, rangeEnd); const QList glyphRuns = line.glyphRuns(posStart, posEnd - posStart + 1); Q_FOREACH (const QGlyphRun &run, glyphRuns) { const QPointF firstPosition = run.positions().first(); const quint32 firstGlyphIndex = run.glyphIndexes().first(); const QPointF lastPosition = run.positions().last(); const quint32 lastGlyphIndex = run.glyphIndexes().last(); const QRawFont rawFont = run.rawFont(); const QRectF firstGlyphRect = rawFont.boundingRect(firstGlyphIndex).translated(firstPosition); const QRectF lastGlyphRect = rawFont.boundingRect(lastGlyphIndex).translated(lastPosition); QRectF rect = run.boundingRect(); /** * HACK ALERT: there is a bug in a way how Qt calculates boundingRect() * of the glyph run. It doesn't care about left and right bearings * of the border chars in the run, therefore it becomes cropped. * * Here we just add a half-char width margin to both sides * of the glyph run to make sure the glyphs are fully painted. * * BUG: 389528 * BUG: 392068 */ rect.setLeft(qMin(rect.left(), lastGlyphRect.left()) - 0.5 * firstGlyphRect.width()); rect.setRight(qMax(rect.right(), lastGlyphRect.right()) + 0.5 * lastGlyphRect.width()); wrapper.addCharacterRect(rect.translated(layoutOffset)); } } } } } -void KoSvgTextShapePrivate::clearAssociatedOutlines(KoShape *rootShape) +void KoSvgTextShape::Private::clearAssociatedOutlines(KoShape *rootShape) { KoSvgTextChunkShape *chunkShape = dynamic_cast(rootShape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->layoutInterface()->clearAssociatedOutline(); Q_FOREACH (KoShape *child, chunkShape->shapes()) { clearAssociatedOutlines(child); } } bool KoSvgTextShape::isRootTextNode() const { return true; } KoSvgTextShapeFactory::KoSvgTextShapeFactory() : KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18n("Text")) { setToolTip(i18n("SVG Text Shape")); setIconName(koIconNameCStr("x-shape-text")); setLoadingPriority(5); setXmlElementNames(KoXmlNS::svg, QStringList("text")); KoShapeTemplate t; t.name = i18n("SVG Text"); t.iconName = koIconName("x-shape-text"); t.toolTip = i18n("SVG Text Shape"); addTemplate(t); } KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const { debugFlake << "Create default svg text shape"; KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "", QRectF(0, 0, 200, 60), documentResources->documentResolution()); debugFlake << converter.errors() << converter.warnings(); return shape; } KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const { KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); QString svgText = params->stringProperty("svgText", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); QString defs = params->stringProperty("defs" , ""); QRectF shapeRect = QRectF(0, 0, 200, 60); QVariant rect = params->property("shapeRect"); if (rect.type()==QVariant::RectF) { shapeRect = rect.toRectF(); } KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg(svgText, defs, shapeRect, documentResources->documentResolution()); shape->setPosition(shapeRect.topLeft()); return shape; } bool KoSvgTextShapeFactory::supports(const KoXmlElement &/*e*/, KoShapeLoadingContext &/*context*/) const { return false; } diff --git a/libs/flake/text/KoSvgTextShape.h b/libs/flake/text/KoSvgTextShape.h index 8a24d8596a..df71aaf783 100644 --- a/libs/flake/text/KoSvgTextShape.h +++ b/libs/flake/text/KoSvgTextShape.h @@ -1,94 +1,94 @@ /* * Copyright (c) 2017 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 KOSVGTEXTSHAPE_H #define KOSVGTEXTSHAPE_H #include "kritaflake_export.h" #include #include #include class KoSvgTextProperties; -class KoSvgTextShapePrivate; #define KoSvgTextShape_SHAPEID "KoSvgTextShapeID" /** * KoSvgTextShape is a root chunk of the \ element subtree. */ class KRITAFLAKE_EXPORT KoSvgTextShape : public KoSvgTextChunkShape { public: KoSvgTextShape(); KoSvgTextShape(const KoSvgTextShape &rhs); ~KoSvgTextShape() override; KoShape* cloneShape() const override; void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override; void paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override; /** * Reset the text shape into initial shape, removing all the child shapes * and precalculated layouts. This method is used by text-updating code to * upload the updated text into shape. The upload code first calls * resetTextShape() and then adds new children. */ void resetTextShape() override; /** * Create a new text layout for the current content of the text shape * chunks tree. The user should always call relayout() after every change * in the text shapes hierarchy. */ void relayout(); QPainterPath textOutline(); protected: /** * Show if the shape is a root of the text hierarchy. Always true for * KoSvgTextShape and always false for KoSvgTextChunkShape */ bool isRootTextNode() const override; void shapeChanged(ChangeType type, KoShape *shape) override; private: - Q_DECLARE_PRIVATE(KoSvgTextShape) + class Private; + QSharedDataPointer d; }; class KoSvgTextShapeFactory : public KoShapeFactoryBase { public: /// constructor KoSvgTextShapeFactory(); ~KoSvgTextShapeFactory() {} KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const override; KoShape *createShape(const KoProperties *params, KoDocumentResourceManager *documentResources = 0) const override; /// Reimplemented bool supports(const KoXmlElement &e, KoShapeLoadingContext &context) const override; }; #endif // KOSVGTEXTSHAPE_H diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp index dae2d081c9..f33a0e3e2f 100644 --- a/libs/flake/tools/KoPathTool.cpp +++ b/libs/flake/tools/KoPathTool.cpp @@ -1,1308 +1,1308 @@ /* This file is part of the KDE project * Copyright (C) 2006-2012 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007, 2010 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathTool.h" #include "KoToolBase_p.h" #include "KoPathShape_p.h" #include "KoPathToolHandle.h" #include "KoCanvasBase.h" #include "KoShapeManager.h" #include "KoSelectedShapesProxy.h" #include "KoDocumentResourceManager.h" #include "KoViewConverter.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "commands/KoPathPointTypeCommand.h" #include "commands/KoPathPointInsertCommand.h" #include "commands/KoPathPointRemoveCommand.h" #include "commands/KoPathSegmentTypeCommand.h" #include "commands/KoPathBreakAtPointCommand.h" #include "commands/KoPathSegmentBreakCommand.h" #include "commands/KoParameterToPathCommand.h" #include "commands/KoSubpathJoinCommand.h" #include #include #include #include "KoParameterShape.h" #include #include "KoPathPoint.h" #include "KoPathPointRubberSelectStrategy.h" #include "KoPathSegmentChangeStrategy.h" #include "KoPathConnectionPointStrategy.h" #include "KoParameterChangeStrategy.h" #include "PathToolOptionWidget.h" #include "KoConnectionShape.h" #include "KoSnapGuide.h" #include "KoShapeController.h" #include "kis_action_registry.h" #include #include #include "kis_command_utils.h" #include "kis_pointer_utils.h" #include #include #include #include #include #include #include #include #include static const unsigned char needle_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e, 0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00 }; static const unsigned char needle_move_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e, 0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00 }; // helper function to calculate the squared distance between two points qreal squaredDistance(const QPointF& p1, const QPointF &p2) { qreal dx = p1.x()-p2.x(); qreal dy = p1.y()-p2.y(); return dx*dx + dy*dy; } struct KoPathTool::PathSegment { PathSegment() : path(0), segmentStart(0), positionOnSegment(0) { } bool isValid() { return path && segmentStart; } KoPathShape *path; KoPathPoint *segmentStart; qreal positionOnSegment; }; KoPathTool::KoPathTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_pointSelection(this) , m_activeHandle(0) , m_handleRadius(3) , m_activeSegment(0) , m_currentStrategy(0) , m_activatedTemporarily(false) { m_points = new QActionGroup(this); // m_pointTypeGroup->setExclusive(true); m_actionPathPointCorner = action("pathpoint-corner"); if (m_actionPathPointCorner) { m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner); m_points->addAction(m_actionPathPointCorner); } m_actionPathPointSmooth = action("pathpoint-smooth"); if (m_actionPathPointSmooth) { m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth); m_points->addAction(m_actionPathPointSmooth); } m_actionPathPointSymmetric = action("pathpoint-symmetric"); if (m_actionPathPointSymmetric) { m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric); m_points->addAction(m_actionPathPointSymmetric); } m_actionCurvePoint = action("pathpoint-curve"); m_actionLinePoint = action("pathpoint-line"); m_actionLineSegment = action("pathsegment-line"); m_actionCurveSegment = action("pathsegment-curve"); m_actionAddPoint = action("pathpoint-insert"); m_actionRemovePoint = action("pathpoint-remove"); m_actionBreakPoint = action("path-break-point"); m_actionBreakSegment = action("path-break-segment"); m_actionJoinSegment = action("pathpoint-join"); m_actionMergePoints = action("pathpoint-merge"); m_actionConvertToPath = action("convert-to-path"); m_contextMenu.reset(new QMenu()); QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits); QBitmap m = b.createHeuristicMask(false); m_selectCursor = QCursor(b, m, 2, 0); b = QBitmap::fromData(QSize(16, 16), needle_move_bits); m = b.createHeuristicMask(false); m_moveCursor = QCursor(b, m, 2, 0); } KoPathTool::~KoPathTool() { delete m_activeHandle; delete m_activeSegment; delete m_currentStrategy; } QList > KoPathTool::createOptionWidgets() { QList > list; PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this); connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int))); connect(this, SIGNAL(singleShapeChanged(KoPathShape*)), toolOptions, SLOT(setCurrentShape(KoPathShape*))); connect(toolOptions, SIGNAL(sigRequestUpdateActions()), this, SLOT(updateActions())); updateOptionsWidget(); toolOptions->setWindowTitle(i18n("Edit Shape")); list.append(toolOptions); return list; } void KoPathTool::pointTypeChanged(QAction *type) { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); KUndo2Command *initialConversionCommand = createPointToCurveCommand(selectedPoints); // conversion should happen before the c-tor // of KoPathPointTypeCommand is executed! if (initialConversionCommand) { initialConversionCommand->redo(); } KUndo2Command *command = new KoPathPointTypeCommand(selectedPoints, static_cast(type->data().toInt())); if (initialConversionCommand) { using namespace KisCommandUtils; CompositeCommand *parent = new CompositeCommand(); parent->setText(command->text()); parent->addCommand(new SkipFirstRedoWrapper(initialConversionCommand)); parent->addCommand(command); command = parent; } d->canvas->addCommand(command); } } void KoPathTool::insertPoints() { Q_D(KoToolBase); QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { qreal positionInSegment = 0.5; if (m_activeSegment && m_activeSegment->isValid()) { positionInSegment = m_activeSegment->positionOnSegment; } KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, positionInSegment); d->canvas->addCommand(cmd); // TODO: this construction is dangerous. The canvas can remove the command right after // it has been added to it! m_pointSelection.clear(); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } } } void KoPathTool::removePoints() { Q_D(KoToolBase); if (m_pointSelection.size() > 0) { KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController()); PointHandle *pointHandle = dynamic_cast(m_activeHandle); if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) { delete m_activeHandle; m_activeHandle = 0; } clearActivePointSelectionReferences(); d->canvas->addCommand(cmd); } } void KoPathTool::pointToLine() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (point->activeControlPoint1() || point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line)); } } } void KoPathTool::pointToCurve() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); KUndo2Command *command = createPointToCurveCommand(selectedPoints); if (command) { d->canvas->addCommand(command); } } } KUndo2Command* KoPathTool::createPointToCurveCommand(const QList &points) { KUndo2Command *command = 0; QList pointToChange; QList::const_iterator it(points.constBegin()); for (; it != points.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2())) pointToChange.append(*it); } if (!pointToChange.isEmpty()) { command = new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve); } return command; } void KoPathTool::segmentToLine() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line)); } } } void KoPathTool::segmentToCurve() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve)); } } } void KoPathTool::convertToPath() { Q_D(KoToolBase); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); QList parameterShapes; Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameteric = dynamic_cast(shape); if (parameteric && parameteric->isParametricShape()) { parameterShapes.append(parameteric); } } if (!parameterShapes.isEmpty()) { d->canvas->addCommand(new KoParameterToPathCommand(parameterShapes)); } QList textShapes; Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) { if (KoSvgTextShape *text = dynamic_cast(shape)) { textShapes.append(text); } } if (!textShapes.isEmpty()) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Convert to Path")); // TODO: reuse the text from KoParameterToPathCommand const QList oldSelectedShapes = implicitCastList(textShapes); new KoKeepShapesSelectedCommand(oldSelectedShapes, {}, canvas()->selectedShapesProxy(), KisCommandUtils::FlipFlopCommand::State::INITIALIZING, cmd); QList newSelectedShapes; Q_FOREACH (KoSvgTextShape *shape, textShapes) { const QPainterPath path = shape->textOutline(); if (path.isEmpty()) continue; KoPathShape *pathShape = KoPathShape::createShapeFromPainterPath(path); pathShape->setBackground(shape->background()); pathShape->setStroke(shape->stroke()); pathShape->setZIndex(shape->zIndex()); pathShape->setTransformation(shape->transformation()); KoShapeContainer *parent = shape->parent(); canvas()->shapeController()->addShapeDirect(pathShape, parent, cmd); newSelectedShapes << pathShape; } canvas()->shapeController()->removeShapes(oldSelectedShapes, cmd); new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), KisCommandUtils::FlipFlopCommand::State::FINALIZING, cmd); canvas()->addCommand(cmd); } updateOptionsWidget(); } namespace { bool checkCanJoinToPoints(const KoPathPointData & pd1, const KoPathPointData & pd2) { const KoPathPointIndex & index1 = pd1.pointIndex; const KoPathPointIndex & index2 = pd2.pointIndex; KoPathShape *path1 = pd1.pathShape; KoPathShape *path2 = pd2.pathShape; // check if subpaths are already closed if (path1->isClosedSubpath(index1.first) || path2->isClosedSubpath(index2.first)) return false; // check if first point is an endpoint if (index1.second != 0 && index1.second != path1->subpathPointCount(index1.first)-1) return false; // check if second point is an endpoint if (index2.second != 0 && index2.second != path2->subpathPointCount(index2.first)-1) return false; return true; } } void KoPathTool::mergePointsImpl(bool doJoin) { Q_D(KoToolBase); if (m_pointSelection.size() != 2) return; QList pointData = m_pointSelection.selectedPointsData(); if (pointData.size() != 2) return; const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); if (!checkCanJoinToPoints(pd1, pd2)) { return; } clearActivePointSelectionReferences(); KUndo2Command *cmd = 0; if (doJoin) { cmd = new KoMultiPathPointJoinCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection()); } else { cmd = new KoMultiPathPointMergeCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection()); } d->canvas->addCommand(cmd); } void KoPathTool::joinPoints() { mergePointsImpl(true); } void KoPathTool::mergePoints() { mergePointsImpl(false); } void KoPathTool::breakAtPoint() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData())); } } void KoPathTool::breakAtSegment() { Q_D(KoToolBase); // only try to break a segment when 2 points of the same object are selected if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0))); } } } void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoToolBase); Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::primarySelection()); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { parameterShape->paintHandles(helper); } else { shape->paintPoints(helper); } if (!shape->stroke() || !shape->stroke()->isVisible()) { helper.setHandleStyle(KisHandleStyle::secondarySelection()); helper.drawPath(shape->outline()); } } if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } m_pointSelection.paint(painter, converter, m_handleRadius); if (m_activeHandle) { if (m_activeHandle->check(m_pointSelection.selectedShapes())) { m_activeHandle->paint(painter, converter, m_handleRadius); } else { delete m_activeHandle; m_activeHandle = 0; } } else if (m_activeSegment && m_activeSegment->isValid()) { KoPathShape *shape = m_activeSegment->path; // if the stroke is invisible, then we already painted the outline of the shape! if (shape->stroke() && shape->stroke()->isVisible()) { KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart); KoPathSegment segment = shape->segmentByIndex(index).toCubic(); KIS_SAFE_ASSERT_RECOVER_RETURN(segment.isValid()); KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius); helper.setHandleStyle(KisHandleStyle::secondarySelection()); QPainterPath path; path.moveTo(segment.first()->point()); path.cubicTo(segment.first()->controlPoint2(), segment.second()->controlPoint1(), segment.second()->point()); helper.drawPath(path); } } if (m_currentStrategy) { painter.save(); KoShape::applyConversion(painter, converter); d->canvas->snapGuide()->paint(painter, converter); painter.restore(); } } void KoPathTool::repaintDecorations() { Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) { repaint(shape->boundingRect()); } m_pointSelection.repaint(); updateOptionsWidget(); } void KoPathTool::mousePressEvent(KoPointerEvent *event) { // we are moving if we hit a point and use the left mouse button event->ignore(); if (m_activeHandle) { m_currentStrategy = m_activeHandle->handleMousePress(event); event->accept(); } else { if (event->button() & Qt::LeftButton) { // check if we hit a path segment if (m_activeSegment && m_activeSegment->isValid()) { KoPathShape *shape = m_activeSegment->path; KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart); KoPathSegment segment = shape->segmentByIndex(index); m_pointSelection.add(segment.first(), !(event->modifiers() & Qt::ShiftModifier)); m_pointSelection.add(segment.second(), false); KoPathPointData data(shape, index); m_currentStrategy = new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment); event->accept(); } else { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = shapeManager->selection(); KoShape *shape = shapeManager->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } else { KIS_ASSERT_RECOVER_RETURN(m_currentStrategy == 0); m_currentStrategy = new KoPathPointRubberSelectStrategy(this, event->point); event->accept(); } } } } } void KoPathTool::mouseMoveEvent(KoPointerEvent *event) { if (event->button() & Qt::RightButton) return; if (m_currentStrategy) { m_lastPoint = event->point; m_currentStrategy->handleMouseMove(event->point, event->modifiers()); // repaint new handle positions m_pointSelection.repaint(); if (m_activeHandle) { m_activeHandle->repaint(); } if (m_activeSegment) { repaintSegment(m_activeSegment); } return; } if (m_activeSegment) { KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart); KoPathSegment segment = m_activeSegment->path->segmentByIndex(index); repaint(segment.boundingRect()); delete m_activeSegment; m_activeSegment = 0; } Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF roi = handleGrabRect(shape->documentToShape(event->point)); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { int handleId = parameterShape->handleIdAt(roi); if (handleId != -1) { useCursor(m_moveCursor); emit statusTextChanged(i18n("Drag to move handle.")); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; if (KoConnectionShape * connectionShape = dynamic_cast(parameterShape)) { //debugFlake << "handleId" << handleId; m_activeHandle = new ConnectionHandle(this, connectionShape, handleId); m_activeHandle->repaint(); return; } else { //debugFlake << "handleId" << handleId; m_activeHandle = new ParameterHandle(this, parameterShape, handleId); m_activeHandle->repaint(); return; } } } else { QList points = shape->pointsAt(roi); if (! points.empty()) { // find the nearest control point from all points within the roi KoPathPoint * bestPoint = 0; KoPathPoint::PointType bestPointType = KoPathPoint::Node; qreal minDistance = HUGE_VAL; Q_FOREACH (KoPathPoint *p, points) { // the node point must be hit if the point is not selected yet if (! m_pointSelection.contains(p) && ! roi.contains(p->point())) continue; // check for the control points first as otherwise it is no longer // possible to change the control points when they are the same as the point if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) { qreal dist = squaredDistance(roi.center(), p->controlPoint1()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint1; minDistance = dist; } } if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) { qreal dist = squaredDistance(roi.center(), p->controlPoint2()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint2; minDistance = dist; } } // check the node point at last qreal dist = squaredDistance(roi.center(), p->point()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::Node; minDistance = dist; } } if (! bestPoint) return; useCursor(m_moveCursor); if (bestPointType == KoPathPoint::Node) emit statusTextChanged(i18n("Drag to move point. Shift click to change point type.")); else emit statusTextChanged(i18n("Drag to move control point.")); PointHandle *prev = dynamic_cast(m_activeHandle); if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType) return; // no change; if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = new PointHandle(this, bestPoint, bestPointType); m_activeHandle->repaint(); return; } } } useCursor(m_selectCursor); if (m_activeHandle) { m_activeHandle->repaint(); } delete m_activeHandle; m_activeHandle = 0; PathSegment *hoveredSegment = segmentAtPoint(event->point); if(hoveredSegment) { useCursor(Qt::PointingHandCursor); emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point.")); m_activeSegment = hoveredSegment; repaintSegment(m_activeSegment); } else { uint selectedPointCount = m_pointSelection.size(); if (selectedPointCount == 0) emit statusTextChanged(QString()); else if (selectedPointCount == 1) emit statusTextChanged(i18n("Press B to break path at selected point.")); else emit statusTextChanged(i18n("Press B to break path at selected segments.")); } } void KoPathTool::repaintSegment(PathSegment *pathSegment) { if (!pathSegment || !pathSegment->isValid()) return; KoPathPointIndex index = pathSegment->path->pathPointIndex(pathSegment->segmentStart); KoPathSegment segment = pathSegment->path->segmentByIndex(index); repaint(segment.boundingRect()); } void KoPathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { const bool hadNoSelection = !m_pointSelection.hasSelection(); m_currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); if (hadNoSelection && dynamic_cast(m_currentStrategy) && !m_pointSelection.hasSelection()) { // the click didn't do anything at all. Allow it to be used by others. event->ignore(); } delete m_currentStrategy; m_currentStrategy = 0; } } void KoPathTool::keyPressEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers()); } break; case Qt::Key_Escape: m_currentStrategy->cancelInteraction(); delete m_currentStrategy; m_currentStrategy = 0; break; default: event->ignore(); return; } } else { switch (event->key()) { #ifndef NDEBUG - case Qt::Key_D: - if (m_pointSelection.objectCount() == 1) { - QList selectedPoints = m_pointSelection.selectedPointsData(); - KoPathShapePrivate *p = static_cast(selectedPoints[0].pathShape->priv()); - p->debugPath(); - } - break; +// case Qt::Key_D: +// if (m_pointSelection.objectCount() == 1) { +// QList selectedPoints = m_pointSelection.selectedPointsData(); +// KoPathShapePrivate *p = static_cast(selectedPoints[0].pathShape->priv()); +// p->debugPath(); +// } +// break; #endif case Qt::Key_B: if (m_pointSelection.size() == 1) breakAtPoint(); else if (m_pointSelection.size() >= 2) breakAtSegment(); break; default: event->ignore(); return; } } event->accept(); } void KoPathTool::keyReleaseEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier); } break; default: break; } } event->accept(); } void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_D(KoToolBase); event->ignore(); // check if we are doing something else at the moment if (m_currentStrategy) return; if (!m_activeHandle && m_activeSegment && m_activeSegment->isValid()) { QList segments; segments.append( KoPathPointData(m_activeSegment->path, m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart))); KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, m_activeSegment->positionOnSegment); d->canvas->addCommand(cmd); m_pointSelection.clear(); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); event->accept(); } else if (!m_activeHandle && !m_activeSegment && m_activatedTemporarily) { emit done(); event->accept(); } else if (!m_activeHandle && !m_activeSegment) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = shapeManager->selection(); selection->deselectAll(); event->accept(); } } KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point) { // the max allowed distance from a segment const QRectF grabRoi = handleGrabRect(point); const qreal distanceThreshold = 0.5 * KisAlgebra2D::maxDimension(grabRoi); QScopedPointer segment(new PathSegment); Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; // convert document point to shape coordinates const QPointF p = shape->documentToShape(point); // our region of interest, i.e. a region around our mouse position const QRectF roi = shape->documentToShape(grabRoi); qreal minDistance = std::numeric_limits::max(); // check all segments of this shape which intersect the region of interest const QList segments = shape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { const qreal nearestPointParam = s.nearestPoint(p); const QPointF nearestPoint = s.pointAt(nearestPointParam); const qreal distance = kisDistance(p, nearestPoint); // are we within the allowed distance ? if (distance > distanceThreshold) continue; // are we closer to the last closest point ? if (distance < minDistance) { segment->path = shape; segment->segmentStart = s.first(); segment->positionOnSegment = nearestPointParam; } } } if (!segment->isValid()) { segment.reset(); } return segment.take(); } void KoPathTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); Q_D(KoToolBase); m_activatedTemporarily = activation == TemporaryActivation; // retrieve the actual global handle radius m_handleRadius = handleRadius(); d->canvas->snapGuide()->reset(); useCursor(m_selectCursor); m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(updateActions())); m_shapeFillResourceConnector.connectToCanvas(d->canvas); initializeWithShapes(shapes.toList()); connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve()), Qt::UniqueConnection); connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine()), Qt::UniqueConnection); connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine()), Qt::UniqueConnection); connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve()), Qt::UniqueConnection); connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints()), Qt::UniqueConnection); connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints()), Qt::UniqueConnection); connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint()), Qt::UniqueConnection); connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment()), Qt::UniqueConnection); connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints()), Qt::UniqueConnection); connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints()), Qt::UniqueConnection); connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath()), Qt::UniqueConnection); connect(m_points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*)), Qt::UniqueConnection); connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged()), Qt::UniqueConnection); } void KoPathTool::slotSelectionChanged() { Q_D(KoToolBase); QList shapes = d->canvas->selectedShapesProxy()->selection()->selectedEditableShapesAndDelegates(); initializeWithShapes(shapes); } void KoPathTool::notifyPathPointsChanged(KoPathShape *shape) { Q_UNUSED(shape); // active handle and selection might have already become invalid, so just // delete them without dereferencing anything... delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; } void KoPathTool::clearActivePointSelectionReferences() { delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; m_pointSelection.clear(); } void KoPathTool::initializeWithShapes(const QList shapes) { QList selectedShapes; Q_FOREACH (KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape && pathShape->isShapeEditable()) { selectedShapes.append(pathShape); } } const QRectF oldBoundingRect = KoShape::boundingRect(implicitCastList(m_pointSelection.selectedShapes())); if (selectedShapes != m_pointSelection.selectedShapes()) { clearActivePointSelectionReferences(); m_pointSelection.setSelectedShapes(selectedShapes); repaintDecorations(); } Q_FOREACH (KoPathShape *shape, selectedShapes) { // as the tool is just in activation repaintDecorations does not yet get called // so we need to use repaint of the tool and it is only needed to repaint the // current canvas repaint(shape->boundingRect()); } repaint(oldBoundingRect); updateOptionsWidget(); updateActions(); } void KoPathTool::updateOptionsWidget() { PathToolOptionWidget::Types type; QList selectedShapes = m_pointSelection.selectedShapes(); Q_FOREACH (KoPathShape *shape, selectedShapes) { KoParameterShape * parameterShape = dynamic_cast(shape); type |= parameterShape && parameterShape->isParametricShape() ? PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath; } emit singleShapeChanged(selectedShapes.size() == 1 ? selectedShapes.first() : 0); emit typeChanged(type); } void KoPathTool::updateActions() { QList pointData = m_pointSelection.selectedPointsData(); bool canBreakAtPoint = false; bool hasNonSmoothPoints = false; bool hasNonSymmetricPoints = false; bool hasNonSplitPoints = false; bool hasNonLinePoints = false; bool hasNonCurvePoints = false; bool canJoinSubpaths = false; if (!pointData.isEmpty()) { Q_FOREACH (const KoPathPointData &pd, pointData) { const int subpathIndex = pd.pointIndex.first; const int pointIndex = pd.pointIndex.second; canBreakAtPoint |= pd.pathShape->isClosedSubpath(subpathIndex) || (pointIndex > 0 && pointIndex < pd.pathShape->subpathPointCount(subpathIndex) - 1); KoPathPoint *point = pd.pathShape->pointByIndex(pd.pointIndex); hasNonSmoothPoints |= !(point->properties() & KoPathPoint::IsSmooth); hasNonSymmetricPoints |= !(point->properties() & KoPathPoint::IsSymmetric); hasNonSplitPoints |= point->properties() & KoPathPoint::IsSymmetric || point->properties() & KoPathPoint::IsSmooth; hasNonLinePoints |= point->activeControlPoint1() || point->activeControlPoint2(); hasNonCurvePoints |= !point->activeControlPoint1() && !point->activeControlPoint2(); } if (pointData.size() == 2) { const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); canJoinSubpaths = checkCanJoinToPoints(pd1, pd2); } } m_actionPathPointCorner->setEnabled(hasNonSplitPoints); m_actionPathPointSmooth->setEnabled(hasNonSmoothPoints); m_actionPathPointSymmetric->setEnabled(hasNonSymmetricPoints); m_actionRemovePoint->setEnabled(!pointData.isEmpty()); m_actionBreakPoint->setEnabled(canBreakAtPoint); m_actionCurvePoint->setEnabled(hasNonCurvePoints); m_actionLinePoint->setEnabled(hasNonLinePoints); m_actionJoinSegment->setEnabled(canJoinSubpaths); m_actionMergePoints->setEnabled(canJoinSubpaths); QList segments(m_pointSelection.selectedSegmentsData()); bool canSplitAtSegment = false; bool canConvertSegmentToLine = false; bool canConvertSegmentToCurve= false; if (!segments.isEmpty()) { canSplitAtSegment = segments.size() == 1; bool hasLines = false; bool hasCurves = false; Q_FOREACH (const KoPathPointData &pd, segments) { KoPathSegment segment = pd.pathShape->segmentByIndex(pd.pointIndex); hasLines |= segment.degree() == 1; hasCurves |= segment.degree() > 1; } canConvertSegmentToLine = !segments.isEmpty() && hasCurves; canConvertSegmentToCurve= !segments.isEmpty() && hasLines; } m_actionAddPoint->setEnabled(canSplitAtSegment); m_actionLineSegment->setEnabled(canConvertSegmentToLine); m_actionCurveSegment->setEnabled(canConvertSegmentToCurve); m_actionBreakSegment->setEnabled(canSplitAtSegment); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); bool haveConvertibleShapes = false; Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); KoSvgTextShape *textShape = dynamic_cast(shape); if (textShape || (parameterShape && parameterShape->isParametricShape())) { haveConvertibleShapes = true; break; } } m_actionConvertToPath->setEnabled(haveConvertibleShapes); } void KoPathTool::deactivate() { Q_D(KoToolBase); m_shapeFillResourceConnector.disconnect(); m_canvasConnections.clear(); m_pointSelection.clear(); m_pointSelection.setSelectedShapes(QList()); delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; delete m_currentStrategy; m_currentStrategy = 0; d->canvas->snapGuide()->reset(); disconnect(m_actionCurvePoint, 0, this, 0); disconnect(m_actionLinePoint, 0, this, 0); disconnect(m_actionLineSegment, 0, this, 0); disconnect(m_actionCurveSegment, 0, this, 0); disconnect(m_actionAddPoint, 0, this, 0); disconnect(m_actionRemovePoint, 0, this, 0); disconnect(m_actionBreakPoint, 0, this, 0); disconnect(m_actionBreakSegment, 0, this, 0); disconnect(m_actionJoinSegment, 0, this, 0); disconnect(m_actionMergePoints, 0, this, 0); disconnect(m_actionConvertToPath, 0, this, 0); disconnect(m_points, 0, this, 0); disconnect(&m_pointSelection, 0, this, 0); KoToolBase::deactivate(); } void KoPathTool::documentResourceChanged(int key, const QVariant & res) { if (key == KoDocumentResourceManager::HandleRadius) { int oldHandleRadius = m_handleRadius; m_handleRadius = res.toUInt(); // repaint with the bigger of old and new handle radius int maxRadius = qMax(m_handleRadius, oldHandleRadius); Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect(); repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius)); } } } void KoPathTool::pointSelectionChanged() { Q_D(KoToolBase); updateActions(); d->canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList()); emit selectionChanged(m_pointSelection.hasSelection()); } void KoPathTool::repaint(const QRectF &repaintRect) { Q_D(KoToolBase); //debugFlake <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius; // widen border to take antialiasing into account qreal radius = m_handleRadius + 1; d->canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } namespace { void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2) { if (a1->isEnabled() || a2->isEnabled()) { menu->addAction(a1); menu->addAction(a2); menu->addSeparator(); } } void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2, QAction *a3) { if (a1->isEnabled() || a2->isEnabled()) { menu->addAction(a1); menu->addAction(a2); menu->addAction(a3); menu->addSeparator(); } } } QMenu *KoPathTool::popupActionsMenu() { if (m_activeHandle) { m_activeHandle->trySelectHandle(); } if (m_activeSegment && m_activeSegment->isValid()) { KoPathShape *shape = m_activeSegment->path; KoPathSegment segment = shape->segmentByIndex(shape->pathPointIndex(m_activeSegment->segmentStart)); m_pointSelection.add(segment.first(), true); m_pointSelection.add(segment.second(), false); } if (m_contextMenu) { m_contextMenu->clear(); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionPathPointCorner, m_actionPathPointSmooth, m_actionPathPointSymmetric); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionCurvePoint, m_actionLinePoint); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionAddPoint, m_actionRemovePoint); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionLineSegment, m_actionCurveSegment); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionBreakPoint, m_actionBreakSegment); addActionsGroupIfEnabled(m_contextMenu.data(), m_actionJoinSegment, m_actionMergePoints); m_contextMenu->addAction(m_actionConvertToPath); m_contextMenu->addSeparator(); } return m_contextMenu.data(); } void KoPathTool::deleteSelection() { removePoints(); } KoToolSelection * KoPathTool::selection() { return &m_pointSelection; } void KoPathTool::requestUndoDuringStroke() { // noop! } void KoPathTool::requestStrokeCancellation() { explicitUserStrokeEndRequest(); } void KoPathTool::requestStrokeEnd() { // noop! } void KoPathTool::explicitUserStrokeEndRequest() { if (m_activatedTemporarily) { emit done(); } } diff --git a/libs/global/CMakeLists.txt b/libs/global/CMakeLists.txt index 9dbbc2a819..441d87d157 100644 --- a/libs/global/CMakeLists.txt +++ b/libs/global/CMakeLists.txt @@ -1,56 +1,55 @@ add_subdirectory( tests ) include(CheckFunctionExists) check_function_exists(backtrace HAVE_BACKTRACE) configure_file(config-debug.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-debug.h) option(HAVE_MEMORY_LEAK_TRACKER "Enable memory leak tracker (always disabled in release build)" OFF) option(HAVE_BACKTRACE_SUPPORT "Enable recording of backtrace in memory leak tracker" OFF) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-memory-leak-tracker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-memory-leak-tracker.h) ### WRONG PLACE??? set(kritaglobal_LIB_SRCS kis_assert.cpp kis_debug.cpp kis_algebra_2d.cpp kis_memory_leak_tracker.cpp kis_shared.cpp kis_dom_utils.cpp kis_painting_tweaks.cpp KisHandlePainterHelper.cpp KisHandleStyle.cpp - kis_relaxed_timer.cpp kis_signal_compressor.cpp kis_signal_compressor_with_param.cpp kis_thread_safe_signal_compressor.cpp kis_acyclic_signal_connector.cpp kis_latency_tracker.cpp KisQPainterStateSaver.cpp KisSharedThreadPoolAdapter.cpp KisSharedRunnable.cpp KisRollingMeanAccumulatorWrapper.cpp kis_config_notifier.cpp KisDeleteLaterWrapper.cpp KisUsageLogger.cpp KisFileUtils.cpp ) add_library(kritaglobal SHARED ${kritaglobal_LIB_SRCS} ) generate_export_header(kritaglobal BASE_NAME kritaglobal) target_link_libraries(kritaglobal PUBLIC kritaversion Qt5::Concurrent Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Xml KF5::I18n ) set_target_properties(kritaglobal PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaglobal ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/libs/global/KisNewOnCopy.h similarity index 50% copy from plugins/impex/ppm/tests/kis_ppm_test.h copy to libs/global/KisNewOnCopy.h index 827856e793..9ba1641ed8 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.h +++ b/libs/global/KisNewOnCopy.h @@ -1,35 +1,46 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (c) 2019 Tusooa Zhu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_PPM_TEST_H_ -#define _KIS_PPM_TEST_H_ +#ifndef KIS_NEW_ON_COPY_H_ +#define KIS_NEW_ON_COPY_H_ -#include - -class KisPPMTest : public QObject +/** + * This class wraps around some type T that is not copiable. + * When the copy-constructor or assignment of KisNewOnCopy is called, + * it default-constructs an instance of T. + */ +template +class KisNewOnCopy { - Q_OBJECT -private Q_SLOTS: - void testFiles(); +public: + KisNewOnCopy() : instance() {} + KisNewOnCopy(const KisNewOnCopy &) : instance() {} + + // KisNewOnCopy &operator=(const KisNewOnCopy &) { return *this; } + + const T *data() const { return &instance; } + const T *constData() { return &instance; } + T *data() { return &instance; } + const T *operator->() const { return &instance; } + T *operator->() { return &instance; } - void testImportFromWriteonly(); - void testExportToReadonly(); - void testImportIncorrectFormat(); +private: + T instance; }; #endif diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp index fb950e376a..d591fb1200 100644 --- a/libs/global/KisUsageLogger.cpp +++ b/libs/global/KisUsageLogger.cpp @@ -1,164 +1,166 @@ /* * Copyright (c) 2019 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisUsageLogger.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisUsageLogger, s_instance) const QString KisUsageLogger::s_sectionHeader("================================================================================\n"); struct KisUsageLogger::Private { bool active {false}; QFile logFile; }; KisUsageLogger::KisUsageLogger() : d(new Private) { d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); rotateLog(); - d->logFile.open(QFile::Append); + d->logFile.open(QFile::Append | QFile::Text); } KisUsageLogger::~KisUsageLogger() { if (d->active) { close(); } } void KisUsageLogger::initialize() { s_instance->d->active = true; } void KisUsageLogger::close() { log("Closing."); s_instance->d->active = false; s_instance->d->logFile.flush(); s_instance->d->logFile.close(); } void KisUsageLogger::log(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8()); s_instance->d->logFile.write(": "); write(message); } void KisUsageLogger::write(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(message.toUtf8()); s_instance->d->logFile.write("\n"); s_instance->d->logFile.flush(); } void KisUsageLogger::writeHeader() { Q_ASSERT(s_instance->d->logFile.isOpen()); QString sessionHeader = QString("SESSION: %1. Executing %2\n\n") .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)) .arg(qApp->arguments().join(' ')); QString disclaimer = i18n("WARNING: This file contains information about your system and the\n" "images you have been working with.\n" "\n" "If you have problems with Krita, the Krita developers might ask\n" "you to share this file with them. The information in this file is\n" "not shared automatically with the Krita developers in any way. You\n" "can disable logging to this file in Krita's Configure Krita Dialog.\n" "\n" "Please review the contents of this file before sharing this file with\n" "anyone.\n\n"); QString systemInfo; // NOTE: This is intentionally not translated! // Krita version info systemInfo.append("Krita\n"); systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", ")); systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false"); systemInfo.append("\n\n"); systemInfo.append("Qt\n"); systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); systemInfo.append("\n Version (loaded): ").append(qVersion()); systemInfo.append("\n\n"); // OS information systemInfo.append("OS Information\n"); systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); systemInfo.append("\n\n"); s_instance->d->logFile.write(s_sectionHeader.toUtf8()); s_instance->d->logFile.write(sessionHeader.toUtf8()); s_instance->d->logFile.write(disclaimer.toUtf8()); s_instance->d->logFile.write(systemInfo.toUtf8()); } void KisUsageLogger::rotateLog() { - d->logFile.open(QFile::ReadOnly); - QString log = QString::fromUtf8(d->logFile.readAll()); - int sectionCount = log.count(s_sectionHeader); - int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); - while(sectionCount >= s_maxLogs) { - log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); - nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); - sectionCount = log.count(s_sectionHeader); + if (d->logFile.exists()) { + d->logFile.open(QFile::ReadOnly); + QString log = QString::fromUtf8(d->logFile.readAll()); + int sectionCount = log.count(s_sectionHeader); + int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); + while(sectionCount >= s_maxLogs) { + log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); + nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); + sectionCount = log.count(s_sectionHeader); + } + d->logFile.close(); + d->logFile.open(QFile::WriteOnly); + d->logFile.write(log.toUtf8()); + d->logFile.close(); } - d->logFile.close(); - d->logFile.open(QFile::WriteOnly); - d->logFile.write(log.toUtf8()); - d->logFile.close(); } diff --git a/libs/global/kis_global.h b/libs/global/kis_global.h index 0df49fc58b..6316973033 100644 --- a/libs/global/kis_global.h +++ b/libs/global/kis_global.h @@ -1,271 +1,265 @@ /* * Copyright (c) 2000 Matthias Elter * 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 KISGLOBAL_H_ #define KISGLOBAL_H_ #include #include #include "kis_assert.h" #include #include const quint8 quint8_MAX = UCHAR_MAX; const quint16 quint16_MAX = 65535; const qint32 qint32_MAX = (2147483647); const qint32 qint32_MIN = (-2147483647 - 1); const quint8 MAX_SELECTED = UCHAR_MAX; const quint8 MIN_SELECTED = 0; const quint8 SELECTION_THRESHOLD = 1; enum OutlineStyle { OUTLINE_NONE = 0, OUTLINE_CIRCLE, OUTLINE_FULL, OUTLINE_TILT, N_OUTLINE_STYLE_SIZE }; enum CursorStyle { CURSOR_STYLE_NO_CURSOR = 0, CURSOR_STYLE_TOOLICON, CURSOR_STYLE_POINTER, CURSOR_STYLE_SMALL_ROUND, CURSOR_STYLE_CROSSHAIR, CURSOR_STYLE_TRIANGLE_RIGHTHANDED, CURSOR_STYLE_TRIANGLE_LEFTHANDED, CURSOR_STYLE_BLACK_PIXEL, CURSOR_STYLE_WHITE_PIXEL, N_CURSOR_STYLE_SIZE }; enum OldCursorStyle { OLD_CURSOR_STYLE_TOOLICON = 0, OLD_CURSOR_STYLE_CROSSHAIR = 1, OLD_CURSOR_STYLE_POINTER = 2, OLD_CURSOR_STYLE_OUTLINE = 3, OLD_CURSOR_STYLE_NO_CURSOR = 4, OLD_CURSOR_STYLE_SMALL_ROUND = 5, OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT = 6, OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS = 7, OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED = 8, OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED = 9, OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED = 10, OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED = 11 }; -/* - * Most wacom pads have 512 levels of pressure; Qt only supports 256, and even - * this is downscaled to 127 levels because the line would be too jittery, and - * the amount of masks take too much memory otherwise. - */ -const qint32 PRESSURE_LEVELS = 127; const double PRESSURE_MIN = 0.0; const double PRESSURE_MAX = 1.0; const double PRESSURE_DEFAULT = PRESSURE_MAX; const double PRESSURE_THRESHOLD = 5.0 / 255.0; // copy of lcms.h #define INTENT_PERCEPTUAL 0 #define INTENT_RELATIVE_COLORIMETRIC 1 #define INTENT_SATURATION 2 #define INTENT_ABSOLUTE_COLORIMETRIC 3 #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // converts \p a to [0, 2 * M_PI) range inline qreal normalizeAngle(qreal a) { if (a < 0.0) { a = 2 * M_PI + fmod(a, 2 * M_PI); } return a >= 2 * M_PI ? fmod(a, 2 * M_PI) : a; } // converts \p a to [0, 360.0) range inline qreal normalizeAngleDegrees(qreal a) { if (a < 0.0) { a = 360.0 + fmod(a, 360.0); } return a >= 360.0 ? fmod(a, 360.0) : a; } inline qreal shortestAngularDistance(qreal a, qreal b) { qreal dist = fmod(qAbs(a - b), 2 * M_PI); if (dist > M_PI) dist = 2 * M_PI - dist; return dist; } inline qreal incrementInDirection(qreal a, qreal inc, qreal direction) { qreal b1 = a + inc; qreal b2 = a - inc; qreal d1 = shortestAngularDistance(b1, direction); qreal d2 = shortestAngularDistance(b2, direction); return d1 < d2 ? b1 : b2; } inline qreal bisectorAngle(qreal a, qreal b) { const qreal diff = shortestAngularDistance(a, b); return incrementInDirection(a, 0.5 * diff, b); } template inline PointType snapToClosestAxis(PointType P) { if (qAbs(P.x()) < qAbs(P.y())) { P.setX(0); } else { P.setY(0); } return P; } template inline T pow2(const T& x) { return x * x; } template inline T kisDegreesToRadians(T degrees) { return degrees * M_PI / 180.0; } template inline T kisRadiansToDegrees(T radians) { return radians * 180.0 / M_PI; } template inline T kisGrowRect(const T &rect, U offset) { return rect.adjusted(-offset, -offset, offset, offset); } inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) { return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y())); } inline qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2) { return pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y()); } #include inline qreal kisDistanceToLine(const QPointF &m, const QLineF &line) { const QPointF &p1 = line.p1(); const QPointF &p2 = line.p2(); qreal distance = 0; if (qFuzzyCompare(p1.x(), p2.x())) { distance = qAbs(m.x() - p2.x()); } else if (qFuzzyCompare(p1.y(), p2.y())) { distance = qAbs(m.y() - p2.y()); } else { qreal A = 1; qreal B = - (p1.x() - p2.x()) / (p1.y() - p2.y()); qreal C = - p1.x() - B * p1.y(); distance = qAbs(A * m.x() + B * m.y() + C) / std::sqrt(pow2(A) + pow2(B)); } return distance; } inline QPointF kisProjectOnVector(const QPointF &base, const QPointF &v) { const qreal prod = base.x() * v.x() + base.y() * v.y(); const qreal lengthSq = pow2(base.x()) + pow2(base.y()); qreal coeff = prod / lengthSq; return coeff * base; } #include inline QRect kisEnsureInRect(QRect rc, const QRect &bounds) { if(rc.right() > bounds.right()) { rc.translate(bounds.right() - rc.right(), 0); } if(rc.left() < bounds.left()) { rc.translate(bounds.left() - rc.left(), 0); } if(rc.bottom() > bounds.bottom()) { rc.translate(0, bounds.bottom() - rc.bottom()); } if(rc.top() < bounds.top()) { rc.translate(0, bounds.top() - rc.top()); } return rc; } #include "kis_pointer_utils.h" /** * A special wrapper object that converts Qt-style mutexes and locks * into an object that supports Std's (and Boost's) "Lockable" * concept. Basically, it converts tryLock() into try_lock() to comply * with the syntax. */ template struct StdLockableWrapper { StdLockableWrapper(T *lock) : m_lock(lock) {} void lock() { m_lock->lock(); } bool try_lock() { return m_lock->tryLock(); } void unlock() { m_lock->unlock(); } private: T *m_lock; }; #endif // KISGLOBAL_H_ diff --git a/libs/global/kis_relaxed_timer.cpp b/libs/global/kis_relaxed_timer.cpp deleted file mode 100644 index 70a0d70c99..0000000000 --- a/libs/global/kis_relaxed_timer.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2017 Bernhard Liebl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * 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_relaxed_timer.h" -#include "kis_assert.h" - -KisRelaxedTimer::KisRelaxedTimer(QObject *parent) - : QObject(parent) - , m_interval(0) - , m_singleShot(false) - , m_nextTimerTickSeqNo(1) - , m_emitOnTimeTick(0) - , m_isEmitting(false) -{ -} - -void KisRelaxedTimer::setInterval(int interval) -{ - KIS_SAFE_ASSERT_RECOVER(!isActive()) { - this->stop(); - } - - m_interval = interval; -} - -void KisRelaxedTimer::setSingleShot(bool singleShot) -{ - m_singleShot = singleShot; -} - -int KisRelaxedTimer::remainingTime() const -{ - // in contrast to normal QTimers, the remaining time is calculated in - // terms of 2 * m_interval as this is the worst case interval. - - if (!isActive()) { - return -1; - } else { - return qMax(qint64(0), 2 * m_interval - qint64(m_elapsed.elapsed())); - } -} - -void KisRelaxedTimer::start() -{ - m_elapsed.start(); - - // cancels any previously scheduled timer and schedules a new timer to be - // triggered as soon as possible, but never sooner than \p m_interval ms. - - if (!m_timer.isActive()) { - // no internal timer is running. start one, and configure it to send - // us a timeout event on the next possible tick which will be exactly - // \p m_interval ms in the future. - - m_emitOnTimeTick = m_nextTimerTickSeqNo; - m_timer.start(m_interval, this); - } else if (m_isEmitting) { - // an internal timer is running and we are actually called from a - // timeout event. so we know the next tick will happen in exactly - // \p m_interval ms. - - m_emitOnTimeTick = m_nextTimerTickSeqNo; - } else { - // an internal timer is already running, but we do not know when - // the next tick will happen. we need to skip next tick as it - // will be sooner than m_delay. the one after that will be good as - // it will be m_interval * (1 + err) in the future. - - m_emitOnTimeTick = m_nextTimerTickSeqNo + 1; - } -} - -void KisRelaxedTimer::timerEvent(QTimerEvent *event) -{ - Q_UNUSED(event); - - const int ticksStopThreshold = 5; - - const qint64 timerTickSeqNo = m_nextTimerTickSeqNo; - - // from this point on, if this is an emit tick, we are no longer active. - m_nextTimerTickSeqNo++; - - if (timerTickSeqNo == m_emitOnTimeTick) { - if (m_singleShot) { - stop(); - } - const IsEmitting emitting(*this); - emit timeout(); - } else if (timerTickSeqNo - m_emitOnTimeTick > ticksStopThreshold) { - m_timer.stop(); - } -} diff --git a/libs/global/kis_relaxed_timer.h b/libs/global/kis_relaxed_timer.h deleted file mode 100644 index 01a9356ce6..0000000000 --- a/libs/global/kis_relaxed_timer.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2017 Bernhard Liebl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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_RELAXED_TIMER_H -#define __KIS_RELAXED_TIMER_H - -#include -#include -#include - -#include "kritaglobal_export.h" - -/** - * A timer using an interface like QTimer that relaxes the given interval callback - * time guarantees and minimizes internal timer restarts by keeping one long-running - * repeating timer. - * - * Users can use this just like a QTimer. The difference is that KisRelaxedTimer will - * relax the callback guarantee time as follows: timeouts will never happen earlier - * than \p interval ms, but may well happen only after 2 * \p interval ms (whereas - * QTimer guarantees a fixed interval of \p interval ms). - * - * The rationale for using this is that stopping and starting timers can produce a - * measurable performance overhead. KisRelaxedTimer removes that overhead. - */ -class KRITAGLOBAL_EXPORT KisRelaxedTimer : public QObject -{ - Q_OBJECT - -public: - KisRelaxedTimer(QObject *parent = nullptr); - - void start(); - - inline void stop() { - m_emitOnTimeTick = 0; - } - - void setInterval(int interval); - void setSingleShot(bool singleShot); - - inline bool isActive() const { - return m_emitOnTimeTick >= m_nextTimerTickSeqNo; - } - - int remainingTime() const; - -Q_SIGNALS: - void timeout(); - -protected: - void timerEvent(QTimerEvent *event) override; - -private: - int m_interval; - bool m_singleShot; - - QBasicTimer m_timer; - qint64 m_nextTimerTickSeqNo; - qint64 m_emitOnTimeTick; - - QElapsedTimer m_elapsed; - -protected: - class IsEmitting { - public: - IsEmitting(KisRelaxedTimer &timer) : m_timer(timer) { - timer.m_isEmitting = true; - } - - ~IsEmitting() { - m_timer.m_isEmitting = false; - } - - private: - KisRelaxedTimer &m_timer; - }; - - friend class IsEmitting; - - bool m_isEmitting; -}; - -#endif /* __KIS_RELAXED_TIMER_H */ diff --git a/libs/global/kis_signal_compressor.cpp b/libs/global/kis_signal_compressor.cpp index 9cf7936c35..a87039b09b 100644 --- a/libs/global/kis_signal_compressor.cpp +++ b/libs/global/kis_signal_compressor.cpp @@ -1,142 +1,208 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * KisSignalCompressor will never trigger timeout more often than every \p delay ms, * i.e. \p delay ms is a given lower limit defining the highest frequency. * * The current implementation uses a long-running monitor timer to eliminate the * overhead incurred by restarting and stopping timers with each signal. The * consequence of this is that the given \p delay ms is not always exactly followed. * * KisSignalCompressor makes the following callback guarantees (0 < err <= 1, with * err == 0 if this is the first signal after a while): * * POSTPONE: - * - timeout after <= (1 + err) * \p delay ms. + * - timeout after = [0.5 ... 1.0] * \p delay ms. * FIRST_ACTIVE_POSTPONE_NEXT: * - first timeout immediately - * - postponed timeout after (1 + err) * \p delay ms + * - postponed timeout after [0.5 ... 1.0] * \p delay ms * FIRST_ACTIVE: * - first timeout immediately - * - second timeout after (1 + err) * \p delay ms - * - after that: \p delay ms - * FIRST_INACTIVE: - * - timeout after (1 + err) * \p delay ms + * - after that [0.5 ... 1.5] * \p delay ms + * FIRST_INACTIVE: + * - timeout after [0.5 ... 1.5] * \p delay ms */ #include "kis_signal_compressor.h" -#include "kis_relaxed_timer.h" + +#include +#include "kis_assert.h" +#include "kis_debug.h" + KisSignalCompressor::KisSignalCompressor() : QObject(0) - , m_timer(new KisRelaxedTimer(this)) - , m_mode(UNDEFINED) - , m_gotSignals(false) + , m_timer(new QTimer(this)) { - m_timer->setSingleShot(true); + m_timer->setSingleShot(false); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired())); } KisSignalCompressor::KisSignalCompressor(int delay, Mode mode, QObject *parent) : QObject(parent), - m_timer(new KisRelaxedTimer(this)), - m_mode(mode), - m_gotSignals(false) + m_timer(new QTimer(this)), + m_mode(mode) { - m_timer->setSingleShot(true); + m_timer->setSingleShot(false); m_timer->setInterval(delay); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired())); } void KisSignalCompressor::setDelay(int delay) { const bool wasActive = m_timer->isActive(); if (wasActive) { m_timer->stop(); } m_timer->setInterval(delay); if (wasActive) { m_timer->start(); } } void KisSignalCompressor::start() { - Q_ASSERT(m_mode != UNDEFINED); + KIS_SAFE_ASSERT_RECOVER_RETURN(m_mode != UNDEFINED); + + const bool isFirstStart = !m_timer->isActive(); + + KIS_SAFE_ASSERT_RECOVER_NOOP(!isFirstStart || !m_signalsPending); switch (m_mode) { case POSTPONE: - m_timer->start(); + if (isFirstStart) { + m_timer->start(); + } + m_lastEmittedTimer.restart(); + m_signalsPending = true; break; case FIRST_ACTIVE_POSTPONE_NEXT: case FIRST_ACTIVE: - if (!m_timer->isActive()) { - m_gotSignals = false; + if (isFirstStart) { m_timer->start(); - emit timeout(); + m_lastEmittedTimer.restart(); + m_signalsPending = false; + if (!tryEmitSignalSafely()) { + m_signalsPending = true; + } } else { - m_gotSignals = true; - if (m_mode == FIRST_ACTIVE_POSTPONE_NEXT) { - m_timer->start(); - } else if (m_mode == FIRST_ACTIVE && m_timer->remainingTime() == 0) { - // overdue, swamped by other events - m_timer->stop(); - slotTimerExpired(); + if (m_mode == FIRST_ACTIVE) { + m_signalsPending = true; + tryEmitOnTick(false); + } else { + m_lastEmittedTimer.restart(); + m_signalsPending = true; } } break; case FIRST_INACTIVE: - if (!m_timer->isActive()) { + if (isFirstStart) { m_timer->start(); + m_lastEmittedTimer.restart(); + m_signalsPending = true; + } else { + m_signalsPending = true; + tryEmitOnTick(false); } case UNDEFINED: ; // Should never happen, but do nothing }; - if (m_mode == POSTPONE || !m_timer->isActive()) { + KIS_SAFE_ASSERT_RECOVER(m_timer->isActive()) { m_timer->start(); } } -void KisSignalCompressor::slotTimerExpired() +bool KisSignalCompressor::tryEmitOnTick(bool isFromTimer) +{ + bool wasEmitted = false; + + // we have different requirements for hi-frequency events (the mean + // of the events rate must be min(compressorRate, eventsRate) + const int realInterval = m_timer->interval(); + const int minInterval = realInterval < 100 ? 0.5 * realInterval : realInterval; + + // Enable for debugging: + // ENTER_FUNCTION() << ppVar(isFromTimer) << ppVar(m_signalsPending) << m_lastEmittedTimer.elapsed(); + + if (m_signalsPending && m_lastEmittedTimer.elapsed() >= minInterval) { + KIS_SAFE_ASSERT_RECOVER_NOOP(!isFromTimer || !m_isEmitting); + + m_lastEmittedTimer.start(); + m_signalsPending = false; + if (!tryEmitSignalSafely()) { + m_signalsPending = true; + } + wasEmitted = true; + } else if (!isFromTimer) { + m_signalsPending = true; + } + + return wasEmitted; +} + +bool KisSignalCompressor::tryEmitSignalSafely() { - Q_ASSERT(m_mode != UNDEFINED); - if ((m_mode != FIRST_ACTIVE && m_mode != FIRST_ACTIVE_POSTPONE_NEXT) || m_gotSignals) { - m_gotSignals = false; + bool wasEmitted = false; + + m_isEmitting++; + + if (m_isEmitting == 1) { emit timeout(); + wasEmitted = true; + } + + m_isEmitting--; + + return wasEmitted; +} + +void KisSignalCompressor::slotTimerExpired() +{ + KIS_ASSERT_RECOVER_NOOP(m_mode != UNDEFINED); + if (!tryEmitOnTick(true)) { + const int calmDownInterval = 5 * m_timer->interval(); + + if (!m_lastEmittedTimer.isValid() || + m_lastEmittedTimer.elapsed() > calmDownInterval) { + + m_timer->stop(); + } } } void KisSignalCompressor::stop() { m_timer->stop(); + m_signalsPending = false; + m_lastEmittedTimer.invalidate(); } bool KisSignalCompressor::isActive() const { - return m_timer->isActive() && (m_mode != FIRST_ACTIVE || m_gotSignals); + return m_signalsPending && m_timer->isActive(); } void KisSignalCompressor::setMode(KisSignalCompressor::Mode mode) { m_mode = mode; } diff --git a/libs/global/kis_signal_compressor.h b/libs/global/kis_signal_compressor.h index 2c03f7fea9..1b6b9db84b 100644 --- a/libs/global/kis_signal_compressor.h +++ b/libs/global/kis_signal_compressor.h @@ -1,94 +1,102 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SIGNAL_COMPRESSOR_H #define __KIS_SIGNAL_COMPRESSOR_H #include #include "kritaglobal_export.h" -class KisRelaxedTimer; +#include + +class QTimer; /** * Sets a timer to delay or throttle activation of a Qt slot. One example of * where this is used is to limit the amount of expensive redraw activity on the * canvas. * * There are three behaviors to choose from. * * POSTPONE resets the timer after each call. Therefore if the calls are made * quickly enough, the timer will never be activated. * * FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all * the other actions the other action like in POSTPONE. This mode is * used e.g. in move/remove layer functionality. If you remove a * single layer, you'll see the result immediately. But if you want to * remove multiple layers, you should wait until all the actions are * finished. * * FIRST_ACTIVE emits the timeout() event immediately and sets a timer of * duration \p delay. If the compressor is triggered during this time, it will * wait until the end of the delay period to fire the signal. Further events are * ignored until the timer elapses. Think of it as a queue with size 1, and * where the leading element is popped every \p delay ms. * * FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p * delay ms. The compressor becomes inactive and all events are ignored until * the timer has elapsed. * * The current implementation allows the timeout() to be delayed by up to 2 times * \p delay in certain situations (for details see cpp file). */ class KRITAGLOBAL_EXPORT KisSignalCompressor : public QObject { Q_OBJECT public: enum Mode { POSTPONE, /* Calling start() resets the timer to \p delay ms */ FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */ FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */ FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */ UNDEFINED /* KisSignalCompressor is created without an explicit mode */ }; public: KisSignalCompressor(); KisSignalCompressor(int delay, Mode mode, QObject *parent = 0); bool isActive() const; void setMode(Mode mode); public Q_SLOTS: void setDelay(int delay); void start(); void stop(); private Q_SLOTS: void slotTimerExpired(); Q_SIGNALS: void timeout(); private: - KisRelaxedTimer *m_timer; - Mode m_mode; - bool m_gotSignals; + bool tryEmitOnTick(bool isFromTimer); + bool tryEmitSignalSafely(); + +private: + QTimer *m_timer = 0; + Mode m_mode = UNDEFINED; + bool m_signalsPending = false; + QElapsedTimer m_lastEmittedTimer; + int m_isEmitting = 0; }; #endif /* __KIS_SIGNAL_COMPRESSOR_H */ diff --git a/libs/global/tests/CMakeLists.txt b/libs/global/tests/CMakeLists.txt index ca8e3eff8f..48093cba83 100644 --- a/libs/global/tests/CMakeLists.txt +++ b/libs/global/tests/CMakeLists.txt @@ -1,9 +1,10 @@ include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests(KisSharedThreadPoolAdapterTest.cpp KisSignalAutoConnectionTest.cpp + KisSignalCompressorTest.cpp NAME_PREFIX libs-global- LINK_LIBRARIES kritaglobal Qt5::Test) diff --git a/libs/global/tests/KisSignalCompressorTest.cpp b/libs/global/tests/KisSignalCompressorTest.cpp new file mode 100644 index 0000000000..b25f8a7351 --- /dev/null +++ b/libs/global/tests/KisSignalCompressorTest.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisSignalCompressorTest.h" + +#include "QTimer" +#include "kis_signal_compressor.h" + +#include +#include +#include +#include +#include +#include + +#include "kis_debug.h" + +struct CompressorTester : public QObject +{ + Q_OBJECT +public Q_SLOTS: + void start() { + if (!m_timer.isValid()) { + m_timer.start(); + } else { + m_acc(m_timer.restart()); + } + } +public: + void dump(const QString &testName) { + qDebug() << testName + << "cnt:" << boost::accumulators::count(m_acc) + << "min:" << boost::accumulators::min(m_acc) + << "max:" << boost::accumulators::max(m_acc) + << "mean:" << boost::accumulators::mean(m_acc) + << "var:" << std::sqrt(boost::accumulators::variance(m_acc)); + } + +private: + typedef boost::accumulators::stats< + boost::accumulators::tag::min, + boost::accumulators::tag::max, + boost::accumulators::tag::mean, + boost::accumulators::tag::variance> stats; + + boost::accumulators::accumulator_set m_acc; + QElapsedTimer m_timer; +}; + +void testCompression(int timerInterval, int compressorInterval) +{ + CompressorTester tester; + KisSignalCompressor compressor(compressorInterval, KisSignalCompressor::FIRST_ACTIVE); + QTimer timer; + timer.setInterval(timerInterval); + timer.setTimerType(Qt::PreciseTimer); + timer.setSingleShot(false); + + QObject::connect(&timer, SIGNAL(timeout()), &compressor, SLOT(start())); + QObject::connect(&compressor, SIGNAL(timeout()), &tester, SLOT(start())); + + timer.start(); + + QTest::qWait(500); + + + timer.stop(); + QTest::qWait(compressorInterval * 2); + compressor.stop(); + + tester.dump(QString("timer %1 compressor %2").arg(timerInterval).arg(compressorInterval)); + + QTest::qWait(compressorInterval * 10); +} + +void KisSignalCompressorTest::test() +{ + for (int i = 10; i < 50; i++) { + testCompression(i, 25); + } + //testCompression(10, 25); +} + +QTEST_MAIN(KisSignalCompressorTest) + +#include "KisSignalCompressorTest.moc" diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/libs/global/tests/KisSignalCompressorTest.h similarity index 74% rename from plugins/impex/ppm/tests/kis_ppm_test.h rename to libs/global/tests/KisSignalCompressorTest.h index 827856e793..83c5faf9c2 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.h +++ b/libs/global/tests/KisSignalCompressorTest.h @@ -1,35 +1,33 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_PPM_TEST_H_ -#define _KIS_PPM_TEST_H_ +#ifndef KISSIGNALCOMPRESSORTEST_H +#define KISSIGNALCOMPRESSORTEST_H #include +#include -class KisPPMTest : public QObject +class KisSignalCompressorTest : public QObject { Q_OBJECT -private Q_SLOTS: - void testFiles(); - void testImportFromWriteonly(); - void testExportToReadonly(); - void testImportIncorrectFormat(); +private Q_SLOTS: + void test(); }; -#endif +#endif // KISSIGNALCOMPRESSORTEST_H diff --git a/libs/image/3rdparty/einspline/bspline_create.cpp b/libs/image/3rdparty/einspline/bspline_create.cpp index e49e17a85a..982209a5c8 100644 --- a/libs/image/3rdparty/einspline/bspline_create.cpp +++ b/libs/image/3rdparty/einspline/bspline_create.cpp @@ -1,1873 +1,1884 @@ ///////////////////////////////////////////////////////////////////////////// // einspline: a library for creating and evaluating B-splines // // Copyright (C) 2007 Kenneth P. Esler, Jr. // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // // the Free Software Foundation; either version 2 of the License, or // // (at your option) any later version. // // // // This program is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // // GNU General Public License for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program; if not, write to the Free Software // // Foundation, Inc., 51 Franklin Street, Fifth Floor, // // Boston, MA 02110-1301 USA // ///////////////////////////////////////////////////////////////////////////// #include "bspline_create.h" #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #ifndef __USE_XOPEN2K #define __USE_XOPEN2K #endif #include #include #include int posix_memalign(void **memptr, size_t alignment, size_t size); //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //// Helper functions for spline creation //// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// void init_sse_data(); void find_coefs_1d_d (Ugrid grid, BCtype_d bc, double *data, intptr_t dstride, double *coefs, intptr_t cstride); void solve_deriv_interp_1d_s (float bands[], float coefs[], int M, int cstride) { // Solve interpolating equations // First and last rows are different bands[4*(0)+1] /= bands[4*(0)+0]; bands[4*(0)+2] /= bands[4*(0)+0]; bands[4*(0)+3] /= bands[4*(0)+0]; bands[4*(0)+0] = 1.0; bands[4*(1)+1] -= bands[4*(1)+0]*bands[4*(0)+1]; bands[4*(1)+2] -= bands[4*(1)+0]*bands[4*(0)+2]; bands[4*(1)+3] -= bands[4*(1)+0]*bands[4*(0)+3]; bands[4*(0)+0] = 0.0; bands[4*(1)+2] /= bands[4*(1)+1]; bands[4*(1)+3] /= bands[4*(1)+1]; bands[4*(1)+1] = 1.0; // Now do rows 2 through M+1 for (int row=2; row < (M+1); row++) { bands[4*(row)+1] -= bands[4*(row)+0]*bands[4*(row-1)+2]; bands[4*(row)+3] -= bands[4*(row)+0]*bands[4*(row-1)+3]; bands[4*(row)+2] /= bands[4*(row)+1]; bands[4*(row)+3] /= bands[4*(row)+1]; bands[4*(row)+0] = 0.0; bands[4*(row)+1] = 1.0; } // Do last row bands[4*(M+1)+1] -= bands[4*(M+1)+0]*bands[4*(M-1)+2]; bands[4*(M+1)+3] -= bands[4*(M+1)+0]*bands[4*(M-1)+3]; bands[4*(M+1)+2] -= bands[4*(M+1)+1]*bands[4*(M)+2]; bands[4*(M+1)+3] -= bands[4*(M+1)+1]*bands[4*(M)+3]; bands[4*(M+1)+3] /= bands[4*(M+1)+2]; bands[4*(M+1)+2] = 1.0; coefs[(M+1)*cstride] = bands[4*(M+1)+3]; // Now back substitute up for (int row=M; row>0; row--) coefs[row*cstride] = bands[4*(row)+3] - bands[4*(row)+2]*coefs[cstride*(row+1)]; // Finish with first row coefs[0] = bands[4*(0)+3] - bands[4*(0)+1]*coefs[1*cstride] - bands[4*(0)+2]*coefs[2*cstride]; } // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs void solve_periodic_interp_1d_s (float bands[], float coefs[], int M, int cstride) { std::vector lastCol(M); // Now solve: // First and last rows are different bands[4*(0)+2] /= bands[4*(0)+1]; bands[4*(0)+0] /= bands[4*(0)+1]; bands[4*(0)+3] /= bands[4*(0)+1]; bands[4*(0)+1] = 1.0; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*bands[4*(0)+0]; bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(0)+3]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(0)+2]; lastCol[0] = bands[4*(0)+0]; for (int row=1; row < (M-1); row++) { bands[4*(row)+1] -= bands[4*(row)+0] * bands[4*(row-1)+2]; bands[4*(row)+3] -= bands[4*(row)+0] * bands[4*(row-1)+3]; lastCol[row] = -bands[4*(row)+0] * lastCol[row-1]; bands[4*(row)+0] = 0.0; bands[4*(row)+2] /= bands[4*(row)+1]; bands[4*(row)+3] /= bands[4*(row)+1]; lastCol[row] /= bands[4*(row)+1]; bands[4*(row)+1] = 1.0; if (row < (M-2)) { bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(row)+3]; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*lastCol[row]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(row)+2]; } } // Now do last row // The [2] element and [0] element are now on top of each other bands[4*(M-1)+0] += bands[4*(M-1)+2]; bands[4*(M-1)+1] -= bands[4*(M-1)+0] * (bands[4*(M-2)+2]+lastCol[M-2]); bands[4*(M-1)+3] -= bands[4*(M-1)+0] * bands[4*(M-2)+3]; bands[4*(M-1)+3] /= bands[4*(M-1)+1]; coefs[M*cstride] = bands[4*(M-1)+3]; for (int row=M-2; row>=0; row--) coefs[(row+1)*cstride] = bands[4*(row)+3] - bands[4*(row)+2]*coefs[(row+2)*cstride] - lastCol[row]*coefs[M*cstride]; coefs[0*cstride] = coefs[M*cstride]; coefs[(M+1)*cstride] = coefs[1*cstride]; coefs[(M+2)*cstride] = coefs[2*cstride]; } // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs void solve_antiperiodic_interp_1d_s (float bands[], float coefs[], int M, int cstride) { bands[4*0+0] *= -1.0; bands[4*(M-1)+2] *= -1.0; std::vector lastCol(M); // Now solve: // First and last rows are different bands[4*(0)+2] /= bands[4*(0)+1]; bands[4*(0)+0] /= bands[4*(0)+1]; bands[4*(0)+3] /= bands[4*(0)+1]; bands[4*(0)+1] = 1.0; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*bands[4*(0)+0]; bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(0)+3]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(0)+2]; lastCol[0] = bands[4*(0)+0]; for (int row=1; row < (M-1); row++) { bands[4*(row)+1] -= bands[4*(row)+0] * bands[4*(row-1)+2]; bands[4*(row)+3] -= bands[4*(row)+0] * bands[4*(row-1)+3]; lastCol[row] = -bands[4*(row)+0] * lastCol[row-1]; bands[4*(row)+0] = 0.0; bands[4*(row)+2] /= bands[4*(row)+1]; bands[4*(row)+3] /= bands[4*(row)+1]; lastCol[row] /= bands[4*(row)+1]; bands[4*(row)+1] = 1.0; if (row < (M-2)) { bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(row)+3]; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*lastCol[row]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(row)+2]; } } // Now do last row // The [2] element and [0] element are now on top of each other bands[4*(M-1)+0] += bands[4*(M-1)+2]; bands[4*(M-1)+1] -= bands[4*(M-1)+0] * (bands[4*(M-2)+2]+lastCol[M-2]); bands[4*(M-1)+3] -= bands[4*(M-1)+0] * bands[4*(M-2)+3]; bands[4*(M-1)+3] /= bands[4*(M-1)+1]; coefs[M*cstride] = bands[4*(M-1)+3]; for (int row=M-2; row>=0; row--) coefs[(row+1)*cstride] = bands[4*(row)+3] - bands[4*(row)+2]*coefs[(row+2)*cstride] - lastCol[row]*coefs[M*cstride]; coefs[0*cstride] = -coefs[M*cstride]; coefs[(M+1)*cstride] = -coefs[1*cstride]; coefs[(M+2)*cstride] = -coefs[2*cstride]; } #ifdef HIGH_PRECISION void find_coefs_1d_s (Ugrid grid, BCtype_s bc, float *data, intptr_t dstride, float *coefs, intptr_t cstride) { BCtype_d d_bc; double *d_data, *d_coefs; d_bc.lCode = bc.lCode; d_bc.rCode = bc.rCode; d_bc.lVal = bc.lVal; d_bc.rVal = bc.rVal; int M = grid.num, N; if (bc.lCode == PERIODIC || bc.lCode == ANTIPERIODIC) N = M+3; else N = M+2; d_data = new double[N]; d_coefs = new double[N]; for (int i=0; ispcode = U1D; spline->tcode = SINGLE_REAL; spline->xBC = xBC; spline->x_grid = x_grid; // Setup internal variables int M = x_grid.num; int N; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num); N = M+3; } else { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num-1); N = M+2; } x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; #ifndef HAVE_SSE2 spline->coefs = (float*)malloc (sizeof(float)*N); #else posix_memalign ((void**)&spline->coefs, 16, (sizeof(float)*N)); #endif find_coefs_1d_s (spline->x_grid, xBC, data, 1, spline->coefs, 1); init_sse_data(); return spline; } void recompute_UBspline_1d_s (UBspline_1d_s* spline, float *data) { find_coefs_1d_s (spline->x_grid, spline->xBC, data, 1, spline->coefs, 1); } UBspline_2d_s* create_UBspline_2d_s (Ugrid x_grid, Ugrid y_grid, BCtype_s xBC, BCtype_s yBC, float *data) { // Create new spline UBspline_2d_s* restrict spline = new UBspline_2d_s; spline->spcode = U2D; spline->tcode = SINGLE_REAL; spline->xBC = xBC; spline->yBC = yBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Nx, Ny; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (float*)malloc (sizeof(float)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(float)*Nx*Ny); #endif // First, solve in the X-direction for (int iy=0; iyx_grid, spline->xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, spline->yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } init_sse_data(); return spline; } void recompute_UBspline_2d_s (UBspline_2d_s* spline, float *data) { int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Nx, Ny; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; // First, solve in the X-direction for (int iy=0; iyx_grid, spline->xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, spline->yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } } UBspline_3d_s* create_UBspline_3d_s (Ugrid x_grid, Ugrid y_grid, Ugrid z_grid, BCtype_s xBC, BCtype_s yBC, BCtype_s zBC, float *data) { // Create new spline UBspline_3d_s* restrict spline = new UBspline_3d_s; spline->spcode = U3D; spline->tcode = SINGLE_REAL; spline->xBC = xBC; spline->yBC = yBC; spline->zBC = zBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Mz = z_grid.num; int Nx, Ny, Nz; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; if (zBC.lCode == PERIODIC || zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; z_grid.delta = (z_grid.end - z_grid.start)/(double)(Nz-3); z_grid.delta_inv = 1.0/z_grid.delta; spline->z_grid = z_grid; spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = new float[Nx*Ny*Nz]; #else posix_memalign ((void**)&spline->coefs, 16, (sizeof(float)*Nx*Ny*Nz)); #endif // First, solve in the X-direction for (int iy=0; iyx_grid, xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } init_sse_data(); return spline; } void recompute_UBspline_3d_s (UBspline_3d_s* spline, float *data) { int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Mz = spline->z_grid.num; int Nx, Ny, Nz; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; if (spline->zBC.lCode == PERIODIC || spline->zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; // First, solve in the X-direction for (int iy=0; iyx_grid, spline->xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, spline->yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, spline->zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //// Single-Precision, Complex Creation Routines //// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs UBspline_1d_c* create_UBspline_1d_c (Ugrid x_grid, BCtype_c xBC, complex_float *data) { // Create new spline UBspline_1d_c* restrict spline = new UBspline_1d_c; spline->spcode = U1D; spline->tcode = SINGLE_COMPLEX; spline->xBC = xBC; // Setup internal variables int M = x_grid.num; int N; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num); N = M+3; } else { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num-1); N = M+2; } x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; #ifndef HAVE_SSE2 spline->coefs = (complex_float*)malloc (2*sizeof(float)*N); #else posix_memalign ((void**)&spline->coefs, 16, 2*sizeof(float)*N); #endif BCtype_s xBC_r, xBC_i; xBC_r.lCode = xBC.lCode; xBC_r.rCode = xBC.rCode; xBC_r.lVal = xBC.lVal_r; xBC_r.rVal = xBC.rVal_r; xBC_i.lCode = xBC.lCode; xBC_i.rCode = xBC.rCode; xBC_i.lVal = xBC.lVal_i; xBC_i.rVal = xBC.rVal_i; // Real part find_coefs_1d_s (spline->x_grid, xBC_r, (float*)data, 2, (float*)spline->coefs, 2); // Imaginarty part find_coefs_1d_s (spline->x_grid, xBC_i, ((float*)data)+1, 2, ((float*)spline->coefs+1), 2); init_sse_data(); return spline; } void recompute_UBspline_1d_c (UBspline_1d_c* spline, complex_float *data) { BCtype_s xBC_r, xBC_i; xBC_r.lCode = spline->xBC.lCode; xBC_r.rCode = spline->xBC.rCode; xBC_r.lVal = spline->xBC.lVal_r; xBC_r.rVal = spline->xBC.rVal_r; xBC_i.lCode = spline->xBC.lCode; xBC_i.rCode = spline->xBC.rCode; xBC_i.lVal = spline->xBC.lVal_i; xBC_i.rVal = spline->xBC.rVal_i; // Real part find_coefs_1d_s (spline->x_grid, xBC_r, (float*)data, 2, (float*)spline->coefs, 2); // Imaginarty part find_coefs_1d_s (spline->x_grid, xBC_i, ((float*)data)+1, 2, ((float*)spline->coefs+1), 2); } UBspline_2d_c* create_UBspline_2d_c (Ugrid x_grid, Ugrid y_grid, BCtype_c xBC, BCtype_c yBC, complex_float *data) { // Create new spline UBspline_2d_c* restrict spline = new UBspline_2d_c; spline->spcode = U2D; spline->tcode = SINGLE_COMPLEX; spline->xBC = xBC; spline->yBC = yBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Nx, Ny; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (complex_float*)malloc (2*sizeof(float)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, 2*sizeof(float)*Nx*Ny); #endif BCtype_s xBC_r, xBC_i, yBC_r, yBC_i; xBC_r.lCode = xBC.lCode; xBC_r.rCode = xBC.rCode; xBC_r.lVal = xBC.lVal_r; xBC_r.rVal = xBC.rVal_r; xBC_i.lCode = xBC.lCode; xBC_i.rCode = xBC.rCode; xBC_i.lVal = xBC.lVal_i; xBC_i.rVal = xBC.rVal_i; yBC_r.lCode = yBC.lCode; yBC_r.rCode = yBC.rCode; yBC_r.lVal = yBC.lVal_r; yBC_r.rVal = yBC.rVal_r; yBC_i.lCode = yBC.lCode; yBC_i.rCode = yBC.rCode; yBC_i.lVal = yBC.lVal_i; yBC_i.rVal = yBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((float*)data)+doffset, 2*My, (float*)spline->coefs+coffset, 2*Ny); // Imag part find_coefs_1d_s (spline->x_grid, xBC_i, ((float*)data)+doffset+1, 2*My, ((float*)spline->coefs)+coffset+1, 2*Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((float*)spline->coefs)+doffset, 2, ((float*)spline->coefs)+coffset, 2); // Imag part find_coefs_1d_s (spline->y_grid, yBC_i, ((float*)spline->coefs)+doffset+1, 2, ((float*)spline->coefs)+coffset+1, 2); } init_sse_data(); return spline; } void recompute_UBspline_2d_c (UBspline_2d_c* spline, complex_float *data) { // Setup internal variables int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Nx, Ny; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; BCtype_s xBC_r, xBC_i, yBC_r, yBC_i; xBC_r.lCode = spline->xBC.lCode; xBC_r.rCode = spline->xBC.rCode; xBC_r.lVal = spline->xBC.lVal_r; xBC_r.rVal = spline->xBC.rVal_r; xBC_i.lCode = spline->xBC.lCode; xBC_i.rCode = spline->xBC.rCode; xBC_i.lVal = spline->xBC.lVal_i; xBC_i.rVal = spline->xBC.rVal_i; yBC_r.lCode = spline->yBC.lCode; yBC_r.rCode = spline->yBC.rCode; yBC_r.lVal = spline->yBC.lVal_r; yBC_r.rVal = spline->yBC.rVal_r; yBC_i.lCode = spline->yBC.lCode; yBC_i.rCode = spline->yBC.rCode; yBC_i.lVal = spline->yBC.lVal_i; yBC_i.rVal = spline->yBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((float*)data)+doffset, 2*My, (float*)spline->coefs+coffset, 2*Ny); // Imag part find_coefs_1d_s (spline->x_grid, xBC_i, ((float*)data)+doffset+1, 2*My, ((float*)spline->coefs)+coffset+1, 2*Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((float*)spline->coefs)+doffset, 2, ((float*)spline->coefs)+coffset, 2); // Imag part find_coefs_1d_s (spline->y_grid, yBC_i, ((float*)spline->coefs)+doffset+1, 2, ((float*)spline->coefs)+coffset+1, 2); } } UBspline_3d_c* create_UBspline_3d_c (Ugrid x_grid, Ugrid y_grid, Ugrid z_grid, BCtype_c xBC, BCtype_c yBC, BCtype_c zBC, complex_float *data) { // Create new spline UBspline_3d_c* restrict spline = new UBspline_3d_c; spline->spcode = U3D; spline->tcode = SINGLE_COMPLEX; spline->xBC = xBC; spline->yBC = yBC; spline->zBC = zBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Mz = z_grid.num; int Nx, Ny, Nz; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; if (zBC.lCode == PERIODIC || zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; z_grid.delta = (z_grid.end - z_grid.start)/(double)(Nz-3); z_grid.delta_inv = 1.0/z_grid.delta; spline->z_grid = z_grid; spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = new complex_float[Nx*Ny*Nz]; #else posix_memalign ((void**)&spline->coefs, 16, 2*sizeof(float)*Nx*Ny*Nz); #endif BCtype_s xBC_r, xBC_i, yBC_r, yBC_i, zBC_r, zBC_i; xBC_r.lCode = xBC.lCode; xBC_r.rCode = xBC.rCode; xBC_r.lVal = xBC.lVal_r; xBC_r.rVal = xBC.rVal_r; xBC_i.lCode = xBC.lCode; xBC_i.rCode = xBC.rCode; xBC_i.lVal = xBC.lVal_i; xBC_i.rVal = xBC.rVal_i; yBC_r.lCode = yBC.lCode; yBC_r.rCode = yBC.rCode; yBC_r.lVal = yBC.lVal_r; yBC_r.rVal = yBC.rVal_r; yBC_i.lCode = yBC.lCode; yBC_i.rCode = yBC.rCode; yBC_i.lVal = yBC.lVal_i; yBC_i.rVal = yBC.rVal_i; zBC_r.lCode = zBC.lCode; zBC_r.rCode = zBC.rCode; zBC_r.lVal = zBC.lVal_r; zBC_r.rVal = zBC.rVal_r; zBC_i.lCode = zBC.lCode; zBC_i.rCode = zBC.rCode; zBC_i.lVal = zBC.lVal_i; zBC_i.rVal = zBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((float*)data)+doffset, 2*My*Mz, ((float*)spline->coefs)+coffset, 2*Ny*Nz); // Imag part find_coefs_1d_s (spline->x_grid, xBC_i, ((float*)data)+doffset+1, 2*My*Mz, ((float*)spline->coefs)+coffset+1, 2*Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((float*)spline->coefs)+doffset, 2*Nz, ((float*)spline->coefs)+coffset, 2*Nz); // Imag part find_coefs_1d_s (spline->y_grid, yBC_i, ((float*)spline->coefs)+doffset+1, 2*Nz, ((float*)spline->coefs)+coffset+1, 2*Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, zBC_r, ((float*)spline->coefs)+doffset, 2, ((float*)spline->coefs)+coffset, 2); // Imag part find_coefs_1d_s (spline->z_grid, zBC_i, ((float*)spline->coefs)+doffset+1, 2, ((float*)spline->coefs)+coffset+1, 2); } init_sse_data(); return spline; } void recompute_UBspline_3d_c (UBspline_3d_c* spline, complex_float *data) { // Setup internal variables int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Mz = spline->z_grid.num; int Nx, Ny, Nz; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; if (spline->zBC.lCode == PERIODIC || spline->zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; BCtype_s xBC_r, xBC_i, yBC_r, yBC_i, zBC_r, zBC_i; xBC_r.lCode = spline->xBC.lCode; xBC_r.rCode = spline->xBC.rCode; xBC_r.lVal = spline->xBC.lVal_r; xBC_r.rVal = spline->xBC.rVal_r; xBC_i.lCode = spline->xBC.lCode; xBC_i.rCode = spline->xBC.rCode; xBC_i.lVal = spline->xBC.lVal_i; xBC_i.rVal = spline->xBC.rVal_i; yBC_r.lCode = spline->yBC.lCode; yBC_r.rCode = spline->yBC.rCode; yBC_r.lVal = spline->yBC.lVal_r; yBC_r.rVal = spline->yBC.rVal_r; yBC_i.lCode = spline->yBC.lCode; yBC_i.rCode = spline->yBC.rCode; yBC_i.lVal = spline->yBC.lVal_i; yBC_i.rVal = spline->yBC.rVal_i; zBC_r.lCode = spline->zBC.lCode; zBC_r.rCode = spline->zBC.rCode; zBC_r.lVal = spline->zBC.lVal_r; zBC_r.rVal = spline->zBC.rVal_r; zBC_i.lCode = spline->zBC.lCode; zBC_i.rCode = spline->zBC.rCode; zBC_i.lVal = spline->zBC.lVal_i; zBC_i.rVal = spline->zBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((float*)data)+doffset, 2*My*Mz, ((float*)spline->coefs)+coffset, 2*Ny*Nz); // Imag part find_coefs_1d_s (spline->x_grid, xBC_i, ((float*)data)+doffset+1, 2*My*Mz, ((float*)spline->coefs)+coffset+1, 2*Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((float*)spline->coefs)+doffset, 2*Nz, ((float*)spline->coefs)+coffset, 2*Nz); // Imag part find_coefs_1d_s (spline->y_grid, yBC_i, ((float*)spline->coefs)+doffset+1, 2*Nz, ((float*)spline->coefs)+coffset+1, 2*Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, zBC_r, ((float*)spline->coefs)+doffset, 2, ((float*)spline->coefs)+coffset, 2); // Imag part find_coefs_1d_s (spline->z_grid, zBC_i, ((float*)spline->coefs)+doffset+1, 2, ((float*)spline->coefs)+coffset+1, 2); } } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //// Double-Precision, Real Creation Routines //// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs void solve_deriv_interp_1d_d (double bands[], double coefs[], int M, int cstride) { // Solve interpolating equations // First and last rows are different bands[4*(0)+1] /= bands[4*(0)+0]; bands[4*(0)+2] /= bands[4*(0)+0]; bands[4*(0)+3] /= bands[4*(0)+0]; bands[4*(0)+0] = 1.0; bands[4*(1)+1] -= bands[4*(1)+0]*bands[4*(0)+1]; bands[4*(1)+2] -= bands[4*(1)+0]*bands[4*(0)+2]; bands[4*(1)+3] -= bands[4*(1)+0]*bands[4*(0)+3]; bands[4*(0)+0] = 0.0; bands[4*(1)+2] /= bands[4*(1)+1]; bands[4*(1)+3] /= bands[4*(1)+1]; bands[4*(1)+1] = 1.0; // Now do rows 2 through M+1 for (int row=2; row < (M+1); row++) { bands[4*(row)+1] -= bands[4*(row)+0]*bands[4*(row-1)+2]; bands[4*(row)+3] -= bands[4*(row)+0]*bands[4*(row-1)+3]; bands[4*(row)+2] /= bands[4*(row)+1]; bands[4*(row)+3] /= bands[4*(row)+1]; bands[4*(row)+0] = 0.0; bands[4*(row)+1] = 1.0; } // Do last row bands[4*(M+1)+1] -= bands[4*(M+1)+0]*bands[4*(M-1)+2]; bands[4*(M+1)+3] -= bands[4*(M+1)+0]*bands[4*(M-1)+3]; bands[4*(M+1)+2] -= bands[4*(M+1)+1]*bands[4*(M)+2]; bands[4*(M+1)+3] -= bands[4*(M+1)+1]*bands[4*(M)+3]; bands[4*(M+1)+3] /= bands[4*(M+1)+2]; bands[4*(M+1)+2] = 1.0; coefs[(M+1)*cstride] = bands[4*(M+1)+3]; // Now back substitute up for (int row=M; row>0; row--) coefs[row*cstride] = bands[4*(row)+3] - bands[4*(row)+2]*coefs[cstride*(row+1)]; // Finish with first row coefs[0] = bands[4*(0)+3] - bands[4*(0)+1]*coefs[1*cstride] - bands[4*(0)+2]*coefs[2*cstride]; } // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs void solve_periodic_interp_1d_d (double bands[], double coefs[], int M, int cstride) { std::vector lastCol(M); // Now solve: // First and last rows are different bands[4*(0)+2] /= bands[4*(0)+1]; bands[4*(0)+0] /= bands[4*(0)+1]; bands[4*(0)+3] /= bands[4*(0)+1]; bands[4*(0)+1] = 1.0; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*bands[4*(0)+0]; bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(0)+3]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(0)+2]; lastCol[0] = bands[4*(0)+0]; for (int row=1; row < (M-1); row++) { bands[4*(row)+1] -= bands[4*(row)+0] * bands[4*(row-1)+2]; bands[4*(row)+3] -= bands[4*(row)+0] * bands[4*(row-1)+3]; lastCol[row] = -bands[4*(row)+0] * lastCol[row-1]; bands[4*(row)+0] = 0.0; bands[4*(row)+2] /= bands[4*(row)+1]; bands[4*(row)+3] /= bands[4*(row)+1]; lastCol[row] /= bands[4*(row)+1]; bands[4*(row)+1] = 1.0; if (row < (M-2)) { bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(row)+3]; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*lastCol[row]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(row)+2]; } } // Now do last row // The [2] element and [0] element are now on top of each other bands[4*(M-1)+0] += bands[4*(M-1)+2]; bands[4*(M-1)+1] -= bands[4*(M-1)+0] * (bands[4*(M-2)+2]+lastCol[M-2]); bands[4*(M-1)+3] -= bands[4*(M-1)+0] * bands[4*(M-2)+3]; bands[4*(M-1)+3] /= bands[4*(M-1)+1]; coefs[M*cstride] = bands[4*(M-1)+3]; for (int row=M-2; row>=0; row--) coefs[(row+1)*cstride] = bands[4*(row)+3] - bands[4*(row)+2]*coefs[(row+2)*cstride] - lastCol[row]*coefs[M*cstride]; coefs[0*cstride] = coefs[M*cstride]; coefs[(M+1)*cstride] = coefs[1*cstride]; coefs[(M+2)*cstride] = coefs[2*cstride]; } // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs void solve_antiperiodic_interp_1d_d (double bands[], double coefs[], int M, int cstride) { std::vector lastCol(M); bands[4*0+0] *= -1.0; bands[4*(M-1)+2] *= -1.0; // Now solve: // First and last rows are different bands[4*(0)+2] /= bands[4*(0)+1]; bands[4*(0)+0] /= bands[4*(0)+1]; bands[4*(0)+3] /= bands[4*(0)+1]; bands[4*(0)+1] = 1.0; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*bands[4*(0)+0]; bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(0)+3]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(0)+2]; lastCol[0] = bands[4*(0)+0]; for (int row=1; row < (M-1); row++) { bands[4*(row)+1] -= bands[4*(row)+0] * bands[4*(row-1)+2]; bands[4*(row)+3] -= bands[4*(row)+0] * bands[4*(row-1)+3]; lastCol[row] = -bands[4*(row)+0] * lastCol[row-1]; bands[4*(row)+0] = 0.0; bands[4*(row)+2] /= bands[4*(row)+1]; bands[4*(row)+3] /= bands[4*(row)+1]; lastCol[row] /= bands[4*(row)+1]; bands[4*(row)+1] = 1.0; if (row < (M-2)) { bands[4*(M-1)+3] -= bands[4*(M-1)+2]*bands[4*(row)+3]; bands[4*(M-1)+1] -= bands[4*(M-1)+2]*lastCol[row]; bands[4*(M-1)+2] = -bands[4*(M-1)+2]*bands[4*(row)+2]; } } // Now do last row // The [2] element and [0] element are now on top of each other bands[4*(M-1)+0] += bands[4*(M-1)+2]; bands[4*(M-1)+1] -= bands[4*(M-1)+0] * (bands[4*(M-2)+2]+lastCol[M-2]); bands[4*(M-1)+3] -= bands[4*(M-1)+0] * bands[4*(M-2)+3]; bands[4*(M-1)+3] /= bands[4*(M-1)+1]; coefs[M*cstride] = bands[4*(M-1)+3]; for (int row=M-2; row>=0; row--) coefs[(row+1)*cstride] = bands[4*(row)+3] - bands[4*(row)+2]*coefs[(row+2)*cstride] - lastCol[row]*coefs[M*cstride]; coefs[0*cstride] = -coefs[M*cstride]; coefs[(M+1)*cstride] = -coefs[1*cstride]; coefs[(M+2)*cstride] = -coefs[2*cstride]; } void find_coefs_1d_d (Ugrid grid, BCtype_d bc, double *data, intptr_t dstride, double *coefs, intptr_t cstride) { int M = grid.num; double basis[4] = {1.0/6.0, 2.0/3.0, 1.0/6.0, 0.0}; if (bc.lCode == PERIODIC || bc.lCode == ANTIPERIODIC) { #ifdef HAVE_C_VARARRAYS double bands[M*4]; #else double *bands = new double[4*M]; #endif for (int i=0; ispcode = U1D; spline->tcode = DOUBLE_REAL; spline->xBC = xBC; // Setup internal variables int M = x_grid.num; int N; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num); N = M+3; } else { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num-1); N = M+2; } x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; #ifndef HAVE_SSE2 spline->coefs = (double*)malloc (sizeof(double)*N); #else posix_memalign ((void**)&spline->coefs, 16, sizeof(double)*N); #endif find_coefs_1d_d (spline->x_grid, xBC, data, 1, spline->coefs, 1); init_sse_data(); return spline; } void recompute_UBspline_1d_d (UBspline_1d_d* spline, double *data) { find_coefs_1d_d (spline->x_grid, spline->xBC, data, 1, spline->coefs, 1); } UBspline_2d_d* create_UBspline_2d_d (Ugrid x_grid, Ugrid y_grid, BCtype_d xBC, BCtype_d yBC, double *data) { // Create new spline UBspline_2d_d* restrict spline = new UBspline_2d_d; spline->spcode = U2D; spline->tcode = DOUBLE_REAL; spline->xBC = xBC; spline->yBC = yBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Nx, Ny; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (double*)malloc (sizeof(double)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, (sizeof(double)*Nx*Ny)); #endif // First, solve in the X-direction for (int iy=0; iyx_grid, xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } init_sse_data(); return spline; } void recompute_UBspline_2d_d (UBspline_2d_d* spline, double *data) { int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Nx, Ny; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; // First, solve in the X-direction for (int iy=0; iyx_grid, spline->xBC, data+doffset, My, spline->coefs+coffset, Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, spline->yBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } } UBspline_3d_d* create_UBspline_3d_d (Ugrid x_grid, Ugrid y_grid, Ugrid z_grid, BCtype_d xBC, BCtype_d yBC, BCtype_d zBC, double *data) { // Create new spline UBspline_3d_d* restrict spline = new UBspline_3d_d; spline->spcode = U3D; spline->tcode = DOUBLE_REAL; spline->xBC = xBC; spline->yBC = yBC; spline->zBC = zBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Mz = z_grid.num; int Nx, Ny, Nz; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; if (zBC.lCode == PERIODIC || zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; z_grid.delta = (z_grid.end - z_grid.start)/(double)(Nz-3); z_grid.delta_inv = 1.0/z_grid.delta; spline->z_grid = z_grid; spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = new double[Nx*Ny*Nz]; #else posix_memalign ((void**)&spline->coefs, 16, (sizeof(double)*Nx*Ny*Nz)); #endif // First, solve in the X-direction for (int iy=0; iyx_grid, xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } init_sse_data(); return spline; } void recompute_UBspline_3d_d (UBspline_3d_d* spline, double *data) { int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Mz = spline->z_grid.num; int Nx, Ny, Nz; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; if (spline->zBC.lCode == PERIODIC || spline->zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; // First, solve in the X-direction for (int iy=0; iyx_grid, spline->xBC, data+doffset, My*Mz, spline->coefs+coffset, Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, spline->yBC, spline->coefs+doffset, Nz, spline->coefs+coffset, Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, spline->zBC, spline->coefs+doffset, 1, spline->coefs+coffset, 1); } } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //// Double-Precision, Complex Creation Routines //// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // On input, bands should be filled with: // row 0 : abcdInitial from boundary conditions // rows 1:M: basis functions in first 3 cols, data in last // row M+1 : abcdFinal from boundary conditions // cstride gives the stride between values in coefs. // On exit, coefs with contain interpolating B-spline coefs UBspline_1d_z* create_UBspline_1d_z (Ugrid x_grid, BCtype_z xBC, complex_double *data) { // Create new spline UBspline_1d_z* restrict spline = new UBspline_1d_z; spline->spcode = U1D; spline->tcode = DOUBLE_COMPLEX; spline->xBC = xBC; // Setup internal variables int M = x_grid.num; int N; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num); N = M+3; } else { x_grid.delta = (x_grid.end-x_grid.start)/(double)(x_grid.num-1); N = M+2; } x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; #ifndef HAVE_SSE2 spline->coefs = (complex_double*)malloc (2*sizeof(double)*N); #else posix_memalign ((void**)&spline->coefs, 16, 2*sizeof(double)*N); #endif BCtype_d xBC_r, xBC_i; xBC_r.lCode = xBC.lCode; xBC_r.rCode = xBC.rCode; xBC_r.lVal = xBC.lVal_r; xBC_r.rVal = xBC.rVal_r; xBC_i.lCode = xBC.lCode; xBC_i.rCode = xBC.rCode; xBC_i.lVal = xBC.lVal_i; xBC_i.rVal = xBC.rVal_i; // Real part find_coefs_1d_d (spline->x_grid, xBC_r, (double*)data, 2, (double*)spline->coefs, 2); // Imaginarty part find_coefs_1d_d (spline->x_grid, xBC_i, ((double*)data)+1, 2, ((double*)spline->coefs)+1, 2); init_sse_data(); return spline; } void recompute_UBspline_1d_z (UBspline_1d_z* spline, complex_double *data) { // int M = spline->x_grid.num; // int N; // if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) // N = M+3; // else // N = M+2; BCtype_d xBC_r, xBC_i; xBC_r.lCode = spline->xBC.lCode; xBC_r.rCode = spline->xBC.rCode; xBC_r.lVal = spline->xBC.lVal_r; xBC_r.rVal = spline->xBC.rVal_r; xBC_i.lCode = spline->xBC.lCode; xBC_i.rCode = spline->xBC.rCode; xBC_i.lVal = spline->xBC.lVal_i; xBC_i.rVal = spline->xBC.rVal_i; // Real part find_coefs_1d_d (spline->x_grid, xBC_r, (double*)data, 2, (double*)spline->coefs, 2); // Imaginarty part find_coefs_1d_d (spline->x_grid, xBC_i, ((double*)data)+1, 2, ((double*)spline->coefs)+1, 2); } UBspline_2d_z* create_UBspline_2d_z (Ugrid x_grid, Ugrid y_grid, BCtype_z xBC, BCtype_z yBC, complex_double *data) { // Create new spline UBspline_2d_z* restrict spline = new UBspline_2d_z; spline->spcode = U2D; spline->tcode = DOUBLE_COMPLEX; spline->xBC = xBC; spline->yBC = yBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Nx, Ny; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; spline->x_stride = Ny; #ifndef HAVE_SSE2 spline->coefs = (complex_double*)malloc (2*sizeof(double)*Nx*Ny); #else posix_memalign ((void**)&spline->coefs, 16, 2*sizeof(double)*Nx*Ny); #endif BCtype_d xBC_r, xBC_i, yBC_r, yBC_i; xBC_r.lCode = xBC.lCode; xBC_r.rCode = xBC.rCode; xBC_r.lVal = xBC.lVal_r; xBC_r.rVal = xBC.rVal_r; xBC_i.lCode = xBC.lCode; xBC_i.rCode = xBC.rCode; xBC_i.lVal = xBC.lVal_i; xBC_i.rVal = xBC.rVal_i; yBC_r.lCode = yBC.lCode; yBC_r.rCode = yBC.rCode; yBC_r.lVal = yBC.lVal_r; yBC_r.rVal = yBC.rVal_r; yBC_i.lCode = yBC.lCode; yBC_i.rCode = yBC.rCode; yBC_i.lVal = yBC.lVal_i; yBC_i.rVal = yBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((double*)data+doffset), 2*My, (double*)spline->coefs+coffset, 2*Ny); // Imag part find_coefs_1d_d (spline->x_grid, xBC_i, ((double*)data)+doffset+1, 2*My, ((double*)spline->coefs)+coffset+1, 2*Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((double*)spline->coefs)+doffset, 2, (double*)spline->coefs+coffset, 2); // Imag part find_coefs_1d_d (spline->y_grid, yBC_i, (double*)spline->coefs+doffset+1, 2, ((double*)spline->coefs)+coffset+1, 2); } init_sse_data(); return spline; } void recompute_UBspline_2d_z (UBspline_2d_z* spline, complex_double *data) { int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Nx, Ny; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; BCtype_d xBC_r, xBC_i, yBC_r, yBC_i; xBC_r.lCode = spline->xBC.lCode; xBC_r.rCode = spline->xBC.rCode; xBC_r.lVal = spline->xBC.lVal_r; xBC_r.rVal = spline->xBC.rVal_r; xBC_i.lCode = spline->xBC.lCode; xBC_i.rCode = spline->xBC.rCode; xBC_i.lVal = spline->xBC.lVal_i; xBC_i.rVal = spline->xBC.rVal_i; yBC_r.lCode = spline->yBC.lCode; yBC_r.rCode = spline->yBC.rCode; yBC_r.lVal = spline->yBC.lVal_r; yBC_r.rVal = spline->yBC.rVal_r; yBC_i.lCode = spline->yBC.lCode; yBC_i.rCode = spline->yBC.rCode; yBC_i.lVal = spline->yBC.lVal_i; yBC_i.rVal = spline->yBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((double*)data+doffset), 2*My, (double*)spline->coefs+coffset, 2*Ny); // Imag part find_coefs_1d_d (spline->x_grid, xBC_i, ((double*)data)+doffset+1, 2*My, ((double*)spline->coefs)+coffset+1, 2*Ny); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((double*)spline->coefs)+doffset, 2, (double*)spline->coefs+coffset, 2); // Imag part find_coefs_1d_d (spline->y_grid, yBC_i, (double*)spline->coefs+doffset+1, 2, ((double*)spline->coefs)+coffset+1, 2); } } UBspline_3d_z* create_UBspline_3d_z (Ugrid x_grid, Ugrid y_grid, Ugrid z_grid, BCtype_z xBC, BCtype_z yBC, BCtype_z zBC, complex_double *data) { // Create new spline UBspline_3d_z* restrict spline = new UBspline_3d_z; spline->spcode = U3D; spline->tcode = DOUBLE_COMPLEX; spline->xBC = xBC; spline->yBC = yBC; spline->zBC = zBC; // Setup internal variables int Mx = x_grid.num; int My = y_grid.num; int Mz = z_grid.num; int Nx, Ny, Nz; if (xBC.lCode == PERIODIC || xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; x_grid.delta = (x_grid.end - x_grid.start)/(double)(Nx-3); x_grid.delta_inv = 1.0/x_grid.delta; spline->x_grid = x_grid; if (yBC.lCode == PERIODIC || yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; y_grid.delta = (y_grid.end - y_grid.start)/(double)(Ny-3); y_grid.delta_inv = 1.0/y_grid.delta; spline->y_grid = y_grid; if (zBC.lCode == PERIODIC || zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; z_grid.delta = (z_grid.end - z_grid.start)/(double)(Nz-3); z_grid.delta_inv = 1.0/z_grid.delta; spline->z_grid = z_grid; spline->x_stride = Ny*Nz; spline->y_stride = Nz; #ifndef HAVE_SSE2 spline->coefs = new complex_double[Nx*Ny*Nz]; #else posix_memalign ((void**)&spline->coefs, 16, 2*sizeof(double)*Nx*Ny*Nz); #endif BCtype_d xBC_r, xBC_i, yBC_r, yBC_i, zBC_r, zBC_i; xBC_r.lCode = xBC.lCode; xBC_r.rCode = xBC.rCode; xBC_r.lVal = xBC.lVal_r; xBC_r.rVal = xBC.rVal_r; xBC_i.lCode = xBC.lCode; xBC_i.rCode = xBC.rCode; xBC_i.lVal = xBC.lVal_i; xBC_i.rVal = xBC.rVal_i; yBC_r.lCode = yBC.lCode; yBC_r.rCode = yBC.rCode; yBC_r.lVal = yBC.lVal_r; yBC_r.rVal = yBC.rVal_r; yBC_i.lCode = yBC.lCode; yBC_i.rCode = yBC.rCode; yBC_i.lVal = yBC.lVal_i; yBC_i.rVal = yBC.rVal_i; zBC_r.lCode = zBC.lCode; zBC_r.rCode = zBC.rCode; zBC_r.lVal = zBC.lVal_r; zBC_r.rVal = zBC.rVal_r; zBC_i.lCode = zBC.lCode; zBC_i.rCode = zBC.rCode; zBC_i.lVal = zBC.lVal_i; zBC_i.rVal = zBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((double*)data)+doffset, 2*My*Mz, ((double*)spline->coefs)+coffset, 2*Ny*Nz); // Imag part find_coefs_1d_d (spline->x_grid, xBC_i, ((double*)data)+doffset+1, 2*My*Mz, ((double*)spline->coefs)+coffset+1, 2*Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((double*)spline->coefs)+doffset, 2*Nz, ((double*)spline->coefs)+coffset, 2*Nz); // Imag part find_coefs_1d_d (spline->y_grid, yBC_i, ((double*)spline->coefs)+doffset+1, 2*Nz, ((double*)spline->coefs)+coffset+1, 2*Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, zBC_r, ((double*)spline->coefs)+doffset, 2, ((double*)spline->coefs)+coffset, 2); // Imag part find_coefs_1d_d (spline->z_grid, zBC_i, ((double*)spline->coefs)+doffset+1, 2, ((double*)spline->coefs)+coffset+1, 2); } init_sse_data(); return spline; } void recompute_UBspline_3d_z (UBspline_3d_z* spline, complex_double *data) { // Setup internal variables int Mx = spline->x_grid.num; int My = spline->y_grid.num; int Mz = spline->z_grid.num; int Nx, Ny, Nz; if (spline->xBC.lCode == PERIODIC || spline->xBC.lCode == ANTIPERIODIC) Nx = Mx+3; else Nx = Mx+2; if (spline->yBC.lCode == PERIODIC || spline->yBC.lCode == ANTIPERIODIC) Ny = My+3; else Ny = My+2; if (spline->zBC.lCode == PERIODIC || spline->zBC.lCode == ANTIPERIODIC) Nz = Mz+3; else Nz = Mz+2; BCtype_d xBC_r, xBC_i, yBC_r, yBC_i, zBC_r, zBC_i; xBC_r.lCode = spline->xBC.lCode; xBC_r.rCode = spline->xBC.rCode; xBC_r.lVal = spline->xBC.lVal_r; xBC_r.rVal = spline->xBC.rVal_r; xBC_i.lCode = spline->xBC.lCode; xBC_i.rCode = spline->xBC.rCode; xBC_i.lVal = spline->xBC.lVal_i; xBC_i.rVal = spline->xBC.rVal_i; yBC_r.lCode = spline->yBC.lCode; yBC_r.rCode = spline->yBC.rCode; yBC_r.lVal = spline->yBC.lVal_r; yBC_r.rVal = spline->yBC.rVal_r; yBC_i.lCode = spline->yBC.lCode; yBC_i.rCode = spline->yBC.rCode; yBC_i.lVal = spline->yBC.lVal_i; yBC_i.rVal = spline->yBC.rVal_i; zBC_r.lCode = spline->zBC.lCode; zBC_r.rCode = spline->zBC.rCode; zBC_r.lVal = spline->zBC.lVal_r; zBC_r.rVal = spline->zBC.rVal_r; zBC_i.lCode = spline->zBC.lCode; zBC_i.rCode = spline->zBC.rCode; zBC_i.lVal = spline->zBC.lVal_i; zBC_i.rVal = spline->zBC.rVal_i; // First, solve in the X-direction for (int iy=0; iyx_grid, xBC_r, ((double*)data)+doffset, 2*My*Mz, ((double*)spline->coefs)+coffset, 2*Ny*Nz); // Imag part find_coefs_1d_d (spline->x_grid, xBC_i, ((double*)data)+doffset+1, 2*My*Mz, ((double*)spline->coefs)+coffset+1, 2*Ny*Nz); } // Now, solve in the Y-direction for (int ix=0; ixy_grid, yBC_r, ((double*)spline->coefs)+doffset, 2*Nz, ((double*)spline->coefs)+coffset, 2*Nz); // Imag part find_coefs_1d_d (spline->y_grid, yBC_i, ((double*)spline->coefs)+doffset+1, 2*Nz, ((double*)spline->coefs)+coffset+1, 2*Nz); } // Now, solve in the Z-direction for (int ix=0; ixz_grid, zBC_r, ((double*)spline->coefs)+doffset, 2, ((double*)spline->coefs)+coffset, 2); // Imag part find_coefs_1d_d (spline->z_grid, zBC_i, ((double*)spline->coefs)+doffset+1, 2, ((double*)spline->coefs)+coffset+1, 2); } } void destroy_UBspline (Bspline *spline) { free(spline->coefs); delete spline; } void destroy_NUBspline (Bspline *spline); void destroy_multi_UBspline (Bspline *spline); void destroy_Bspline (void *spline) { Bspline *sp = (Bspline *)spline; if (sp->sp_code <= U3D) destroy_UBspline (sp); else if (sp->sp_code <= NU3D) destroy_NUBspline (sp); else if (sp->sp_code <= MULTI_U3D) destroy_multi_UBspline (sp); else fprintf (stderr, "Error in destroy_Bspline: invalid spline code %d.\n", sp->sp_code); } diff --git a/libs/image/kis_adjustment_layer.h b/libs/image/kis_adjustment_layer.h index 447f0cf422..a9657ac46e 100644 --- a/libs/image/kis_adjustment_layer.h +++ b/libs/image/kis_adjustment_layer.h @@ -1,125 +1,102 @@ /* * Copyright (c) 2006 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. */ - -/** - * @file kis_adjustment_layer.h - * This file is part of the Krita calligra application. It handles - * a contains a KisFilter OR a KisLayer, and this class is created - * to influence the rendering of layers below this one. Can also - * function as a fixating layer. - * - * @author Boudewijn Rempt - * @author comments by hscott - * @since 1.5 - */ #ifndef KIS_ADJUSTMENT_LAYER_H_ #define KIS_ADJUSTMENT_LAYER_H_ #include #include #include "kis_selection_based_layer.h" - class KisFilterConfiguration; -/** - * @class KisAdjustmentLayer - * @brief Contains a KisFilter and a KisSelection. - * - * If the selection is present, it is a mask used by the adjustment layer - * to know where to apply the filter, thus the combination is used - * to influence the rendering of the layers under this layer - * in the layerstack. AdjustmentLayers also function as a kind - * of "fixating layers". - */ class KRITAIMAGE_EXPORT KisAdjustmentLayer : public KisSelectionBasedLayer { Q_OBJECT public: /** * creates a new adjustment layer with the given * configuration and selection. Note that the selection * will be _copied_ (with COW, though). * @param image the image to set this AdjustmentLayer to * @param name name of the adjustment layer * @param kfc the configuration for the adjustment layer filter * @param selection is a mask used by the adjustment layer to * know where to apply the filter. */ KisAdjustmentLayer(KisImageWSP image, const QString &name, KisFilterConfigurationSP kfc, KisSelectionSP selection); KisAdjustmentLayer(const KisAdjustmentLayer& rhs); ~KisAdjustmentLayer() override; bool accept(KisNodeVisitor &) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; /** * clones this AdjustmentLayer into a KisNodeSP type. * @return the KisNodeSP returned */ KisNodeSP clone() const override { return KisNodeSP(new KisAdjustmentLayer(*this)); } /** * gets the adjustmentLayer's tool filter * @return QIcon returns the QIcon tool filter */ QIcon icon() const override; /** * gets the AdjustmentLayer properties describing whether * or not the node is locked, visible, and the filter * name is it is a filter. Overrides sectionModelProperties * in KisLayer, and KisLayer overrides * sectionModelProperties in KisBaseNode. * @return KisBaseNode::PropertyList returns a list * of the properties */ KisBaseNode::PropertyList sectionModelProperties() const override; public: /** * \see KisNodeFilterInterface::setFilter() */ void setFilter(KisFilterConfigurationSP filterConfig) override; void setChannelFlags(const QBitArray & channelFlags) override; protected: // override from KisLayer QRect incomingChangeRect(const QRect &rect) const override; // override from KisNode QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; public Q_SLOTS: /** * gets this AdjustmentLayer. Overrides function in * KisIndirectPaintingSupport * @return this AdjustmentLayer */ KisLayer* layer() { return this; } }; #endif // KIS_ADJUSTMENT_LAYER_H_ diff --git a/libs/image/kis_circle_mask_generator.cpp b/libs/image/kis_circle_mask_generator.cpp index 356c5fc9ca..42f9405217 100644 --- a/libs/image/kis_circle_mask_generator.cpp +++ b/libs/image/kis_circle_mask_generator.cpp @@ -1,143 +1,142 @@ /* * Copyright (c) 2004,2007-2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2012 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wundef" #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #endif #include #include "kis_fast_math.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" #include "kis_base_mask_generator.h" #include "kis_brush_mask_applicator_factories.h" #include "kis_brush_mask_applicator_base.h" KisCircleMaskGenerator::KisCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, DefaultId), d(new Private) { setScale(1.0, 1.0); // store the variable locally to allow vector implementation read it easily d->copyOfAntialiasEdges = antialiasEdges; d->applicator.reset(createOptimizedClass >(this)); } KisCircleMaskGenerator::KisCircleMaskGenerator(const KisCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisCircleMaskGenerator::clone() const { return new KisCircleMaskGenerator(*this); } void KisCircleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->xcoef = 2.0 / effectiveSrcWidth(); d->ycoef = 2.0 / effectiveSrcHeight(); d->xfadecoef = qFuzzyCompare(horizontalFade(), 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth())); d->yfadecoef = qFuzzyCompare(verticalFade() , 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight())); - d->transformedFadeX = KisMaskGenerator::softness() * d->xfadecoef; - d->transformedFadeY = KisMaskGenerator::softness() * d->yfadecoef; - + d->transformedFadeX = d->xfadecoef * d->safeSoftnessCoeff; + d->transformedFadeY = d->yfadecoef * d->safeSoftnessCoeff; } KisCircleMaskGenerator::~KisCircleMaskGenerator() { } bool KisCircleMaskGenerator::shouldSupersample() const { return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } bool KisCircleMaskGenerator::shouldVectorize() const { return !shouldSupersample() && spikes() == 2; } KisBrushMaskApplicatorBase* KisCircleMaskGenerator::applicator() { return d->applicator.data(); } quint8 KisCircleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = (x /*- m_xcenter*/); qreal yr = qAbs(y /*- m_ycenter*/); fixRotation(xr, yr); qreal n = norme(xr * d->xcoef, yr * d->ycoef); if (n > 1.0) return 255; // we add +1.0 to ensure correct antialiasing on the border if (antialiasEdges()) { xr = qAbs(xr) + 1.0; yr = qAbs(yr) + 1.0; } qreal nf = norme(xr * d->transformedFadeX, yr * d->transformedFadeY); if (nf < 1.0) return 0; return 255 * n * (nf - 1.0) / (nf - n); } void KisCircleMaskGenerator::setSoftness(qreal softness) { KisMaskGenerator::setSoftness(softness); - qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); + d->safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); - d->transformedFadeX = d->xfadecoef * safeSoftnessCoeff; - d->transformedFadeY = d->yfadecoef * safeSoftnessCoeff; + d->transformedFadeX = d->xfadecoef * d->safeSoftnessCoeff; + d->transformedFadeY = d->yfadecoef * d->safeSoftnessCoeff; } void KisCircleMaskGenerator::resetMaskApplicator(bool forceScalar) { d->applicator.reset(createOptimizedClass >(this,forceScalar)); } diff --git a/libs/image/kis_circle_mask_generator_p.h b/libs/image/kis_circle_mask_generator_p.h index 096cfa3ad4..71445ace67 100644 --- a/libs/image/kis_circle_mask_generator_p.h +++ b/libs/image/kis_circle_mask_generator_p.h @@ -1,53 +1,56 @@ /* * Copyright (c) 2008-2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CIRCLE_MASK_GENERATOR_P_H_ #define _KIS_CIRCLE_MASK_GENERATOR_P_H_ struct Q_DECL_HIDDEN KisCircleMaskGenerator::Private { Private() : xcoef(0), ycoef(0), xfadecoef(0), yfadecoef(0), + safeSoftnessCoeff(1.0), transformedFadeX(0), transformedFadeY(0), copyOfAntialiasEdges(false) { } Private(const Private &rhs) : xcoef(rhs.xcoef), ycoef(rhs.ycoef), xfadecoef(rhs.xfadecoef), yfadecoef(rhs.yfadecoef), + safeSoftnessCoeff(rhs.safeSoftnessCoeff), transformedFadeX(rhs.transformedFadeX), transformedFadeY(rhs.transformedFadeY), copyOfAntialiasEdges(rhs.copyOfAntialiasEdges) { } double xcoef, ycoef; double xfadecoef, yfadecoef; + qreal safeSoftnessCoeff; double transformedFadeX, transformedFadeY; bool copyOfAntialiasEdges; QScopedPointer applicator; }; #endif /* _KIS_CIRCLE_MASK_GENERATOR_P_H_ */ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index cace3629d0..4d68fc5172 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1961 +1,2029 @@ /* * 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 "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_projection_leaf.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_simple_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 "tiles3/kis_lockless_stack.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) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); 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 KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisLocklessStack savedDisabledUIUpdates; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool allowMasksOnRootNode = false; 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()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); 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 } +KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore) +{ + const KoColorSpace *colorSpace = 0; + + switch (image.format()) { + case QImage::Format_Invalid: + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + colorSpace = KoColorSpaceRegistry::instance()->graya8(); + break; + case QImage::Format_Indexed8: + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + break; + case QImage::Format_RGB16: + colorSpace = KoColorSpaceRegistry::instance()->rgb16(); + break; + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_RGB666: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_RGB555: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_RGB888: + case QImage::Format_RGB444: + case QImage::Format_ARGB4444_Premultiplied: + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888: + case QImage::Format_RGBA8888_Premultiplied: + colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + break; + case QImage::Format_BGR30: + case QImage::Format_A2BGR30_Premultiplied: + case QImage::Format_RGB30: + case QImage::Format_A2RGB30_Premultiplied: + colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + break; + case QImage::Format_Alpha8: + colorSpace = KoColorSpaceRegistry::instance()->alpha8(); + break; + case QImage::Format_Grayscale8: + colorSpace = KoColorSpaceRegistry::instance()->graya8(); + break; +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + case QImage::Format_Grayscale16: + colorSpace = KoColorSpaceRegistry::instance()->graya16(); + break; +#endif +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + case QImage::Format_RGBX64: + case QImage::Format_RGBA64: + case QImage::Format_RGBA64_Premultiplied: + colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0); + break; +#endif + default: + colorSpace = 0; + } + + KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image")); + KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255); + layer->paintDevice()->convertFromQImage(image, 0, 0, 0); + img->addNode(layer.data(), img->rootLayer().data()); + + return img; +} + KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } void KisImage::copyFromImage(const KisImage &rhs) { copyFromImageImpl(rhs, REPLACE); } void KisImage::copyFromImageImpl(const KisImage &rhs, int policy) { // make sure we choose exactly one from REPLACE and CONSTRUCT KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT)); // only when replacing do we need to emit signals #define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit if (policy & REPLACE) { // if we are constructing the image, these are already set if (m_d->width != rhs.width() || m_d->height != rhs.height()) { m_d->width = rhs.width(); m_d->height = rhs.height(); emit sigSizeChanged(QPointF(), QPointF()); } if (m_d->colorSpace != rhs.colorSpace()) { m_d->colorSpace = rhs.colorSpace(); emit sigColorSpaceChanged(m_d->colorSpace); } } // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool) setObjectName(rhs.objectName()); if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) { m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres); } m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode; if (rhs.m_d->proofingConfig) { KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); if (policy & REPLACE) { setProofingConfiguration(proofingConfig); } else { m_d->proofingConfig = proofingConfig; } } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); bool exactCopy = policy & EXACT_COPY; if (exactCopy || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } }); } KisLayerUtils::recursiveApplyNodes(newRoot, [](KisNodeSP node) { dbgImage << "Node: " << (void *)node.data(); }); m_d->compositions.clear(); Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } EMIT_IF_NEEDED sigLayersChangedAsync(); m_d->nserver = 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; /** * The overlay device is not inherited when cloning the image! */ if (rhs.m_d->overlaySelectionMask) { const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); m_d->rootLayer->setDirty(dirtyRect); } #undef EMIT_IF_NEEDED } 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()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0)); } 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); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data()) && deletedNode == m_d->isolatedRootNode) { emit sigInternalStopIsolatedModeRequested(); } 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()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } 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); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(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, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; QPointF offset; { KisTransformWorker worker(0, scaleX, scaleY, 0, 0, 0, 0, 0.0, 0, 0, 0, 0); QTransform transform = worker.transform(); offset = center - transform.map(center); } KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, offset.x(), offset.y(), filterStrategy); visitor->setSelection(selection); if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection) { // we can either transform (and resize) the whole image or // transform a selection, we cannot do both at the same time KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { selection = 0; } const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); 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(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != baseBounds.width() || newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, 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"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { 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(), radians, true, 0); } void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection) { const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); const QPointF origin = QRectF(baseBounds).center(); //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(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, 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"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, 0); } 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::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } 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(KisNodeSP activeNode) { KisLayerUtils::flattenImage(this, activeNode); } 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; } 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) { emit sigInternalStopIsolatedModeRequested(); 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); setRoot(m_d->rootLayer.data()); this->setDefaultProjectionColor(defaultProjectionColor); } 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(true); 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) { struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } 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) { // update 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(); } QVector KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); QRect rect; QVector postponedUpdates; while (m_d->savedDisabledUIUpdates.pop(rect)) { postponedUpdates.append(rect); } return postponedUpdates; } 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); } else { m_d->savedDisabledUIUpdates.push(rc); } } 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); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } 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 KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } void KisImage::setAllowMasksOnRootNode(bool value) { m_d->allowMasksOnRootNode = value; } bool KisImage::allowMasksOnRootNode() const { return m_d->allowMasksOnRootNode; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index d28a034dfe..4844b6cb1d 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1154 +1,1156 @@ /* * 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_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 KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; class KisPaintDevice; 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: /// @p 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; + static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore); + 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; KisNode* graphOverlayNode() const 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); void copyFromImage(const KisImage &rhs); private: // must specify exactly one from CONSTRUCT or REPLACE. enum CopyPolicy { CONSTRUCT = 1, ///< we are copy-constructing a new KisImage REPLACE = 2, ///< we are replacing the current KisImage with another EXACT_COPY = 4, /// we need an exact copy of the original image }; void copyFromImageImpl(const KisImage &rhs, int policy); public: /** * 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); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; /** * Sets the mask (it must be a part of the node hierarchy already) to be paited on * the top of all layers. This method does all the locking and syncing for you. It * is executed asynchronously. */ void setOverlaySelectionMask(KisSelectionMaskSP mask); /** * \see setOverlaySelectionMask */ KisSelectionMaskSP overlaySelectionMask() const; /** * \see setOverlaySelectionMask */ bool hasOverlaySelectionMask() 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(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param node node to crop * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param center the center of the scaling * @param scaleX x-scale coefficient to be applied to the node * @param scaleY y-scale coefficient to be applied to the node * @param filterStrategy filtering strategy * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @p angleX, @p angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX x-shear given in degrees. * @param angleY y-shear given in degrees. * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection); /** * 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 No 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 * and 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(); /** * 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 rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(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 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(KisNodeSP activeNode); /** * 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 deleted 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 current 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; /** * Configure the image to allow masks on the root not (as reported by * root()->allowAsChild()). By default it is not allowed (because it * looks weird from GUI point of view) */ void setAllowMasksOnRootNode(bool value); /** * \see setAllowMasksOnRootNode() */ bool allowMasksOnRootNode() 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 Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. Parameter is 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(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ 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. * * The last call to enableUIUpdates() will return the list of udpates * that were requested while they were blocked. */ void disableUIUpdates() override; /** * \see disableUIUpdates */ QVector 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 distinguish * 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, double radians, bool resizeImage, KisSelectionSP selection); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection); 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_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp index 3cac42942e..73ae0469fb 100644 --- a/libs/image/kis_selection_based_layer.cpp +++ b/libs/image/kis_selection_based_layer.cpp @@ -1,358 +1,359 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 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_selection_based_layer.h" #include #include "kis_debug.h" #include #include "kis_image.h" #include "kis_painter.h" #include "kis_default_bounds.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" #include "kis_raster_keyframe_channel.h" struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private { public: Private() : useSelectionInProjection(true) {} Private(const Private &rhs) : useSelectionInProjection(rhs.useSelectionInProjection) {} KisSelectionSP selection; KisPaintDeviceSP paintDevice; bool useSelectionInProjection; }; KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig, bool useGeneratorRegistry) : KisLayer(image.data(), name, OPACITY_OPAQUE_U8), KisNodeFilterInterface(filterConfig, useGeneratorRegistry), m_d(new Private()) { if (!selection) { initSelection(); } else { setInternalSelection(selection); } KisImageSP imageSP = image.toStrongRef(); if (!imageSP) { return; } m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(this, imageSP->colorSpace(), KisDefaultBoundsSP(new KisDefaultBounds(image)))); connect(imageSP.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs) : KisLayer(rhs) , KisIndirectPaintingSupport() , KisNodeFilterInterface(rhs) , m_d(new Private(*rhs.m_d)) { setInternalSelection(rhs.m_d->selection); m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data()); } KisSelectionBasedLayer::~KisSelectionBasedLayer() { delete m_d; } void KisSelectionBasedLayer::initSelection() { m_d->selection = KisSelectionSP(new KisSelection(KisDefaultBoundsSP(new KisDefaultBounds(image())))); m_d->selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, m_d->selection->pixelSelection()->colorSpace())); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); } void KisSelectionBasedLayer::slotImageSizeChanged() { if (m_d->selection) { /** * Make sure exactBounds() of the selection got recalculated after * the image changed */ m_d->selection->pixelSelection()->setDirty(); setDirty(); } } void KisSelectionBasedLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); + m_d->selection->pixelSelection()->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); KisLayer::setImage(image); connect(image.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisSelectionBasedLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const { return m_d->selection->pixelSelection(); } bool KisSelectionBasedLayer::needProjection() const { return m_d->selection; } void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const { m_d->useSelectionInProjection = value; } KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const { if (!m_d->selection) return KisSelectionSP(); m_d->selection->updateProjection(rect); KisSelectionSP tempSelection = m_d->selection; KisIndirectPaintingSupport::ReadLocker l(this); if (hasTemporaryTarget()) { /** * WARNING: we don't try to clone the selection entirely, because * it might be unsafe for shape selections. * * TODO: make cloning of vector selections safe! See a comment in * KisShapeSelection::clone(). */ tempSelection = new KisSelection(); KisPainter::copyAreaOptimized(rect.topLeft(), m_d->selection->pixelSelection(), tempSelection->pixelSelection(), rect); KisPainter gc2(tempSelection->pixelSelection()); setupTemporaryPainter(&gc2); gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect); } return tempSelection; } void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisSelectionSP tempSelection; if (m_d->useSelectionInProjection) { tempSelection = fetchComposedInternalSelection(rect); /** * When we paint with a selection, the deselected areas will *not* be * overwritten by copyAreaOptimized(), so we need to clear them beforehand */ projection->clear(rect); } KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection); } QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const { return m_d->selection ? rect & m_d->selection->selectedRect() : rect; } QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } void KisSelectionBasedLayer::resetCache() { KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } if (!m_d->paintDevice || *m_d->paintDevice->colorSpace() != *imageSP->colorSpace()) { m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(KisNodeWSP(this), imageSP->colorSpace(), new KisDefaultBounds(image()))); } else { m_d->paintDevice->clear(); } } KisSelectionSP KisSelectionBasedLayer::internalSelection() const { return m_d->selection; } void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection) { if (selection) { m_d->selection = new KisSelection(*selection.data()); m_d->selection->setParentNode(this); m_d->selection->setDefaultBounds(new KisDefaultBounds(image())); m_d->selection->updateProjection(); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } KisImageSP imageSP = image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(imageSP); if (m_d->selection->pixelSelection()->defaultBounds()->bounds() != imageSP->bounds()) { qWarning() << "WARNING: KisSelectionBasedLayer::setInternalSelection" << "New selection has suspicious default bounds"; qWarning() << "WARNING:" << ppVar(m_d->selection->pixelSelection()->defaultBounds()->bounds()); qWarning() << "WARNING:" << ppVar(imageSP->bounds()); } } else { m_d->selection = 0; } } qint32 KisSelectionBasedLayer::x() const { return m_d->selection ? m_d->selection->x() : 0; } qint32 KisSelectionBasedLayer::y() const { return m_d->selection ? m_d->selection->y() : 0; } void KisSelectionBasedLayer::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } } void KisSelectionBasedLayer::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } } KisKeyframeChannel *KisSelectionBasedLayer::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisRasterKeyframeChannel *contentChannel = m_d->selection->pixelSelection()->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } return KisLayer::requestKeyframeChannel(id); } void KisSelectionBasedLayer::setDirty() { Q_ASSERT(image()); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } setDirty(imageSP->bounds()); } QRect KisSelectionBasedLayer::extent() const { QRect resultRect; if (m_d->selection) { resultRect = m_d->selection->selectedRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { resultRect |= temporaryTarget->extent(); } } else { KisImageSP image = this->image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); resultRect = image->bounds(); } return resultRect; } QRect KisSelectionBasedLayer::exactBounds() const { QRect resultRect; if (m_d->selection) { resultRect = m_d->selection->selectedExactRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { resultRect |= temporaryTarget->exactBounds(); } } else { KisImageSP image = this->image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); resultRect = image->bounds(); } return resultRect; } QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h) { KisSelectionSP originalSelection = internalSelection(); KisPaintDeviceSP originalDevice = original(); return originalDevice && originalSelection ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/libs/image/tests/kis_image_signal_router_test.cpp b/libs/image/tests/kis_image_signal_router_test.cpp index 8a74a0711b..e86db1394a 100644 --- a/libs/image/tests/kis_image_signal_router_test.cpp +++ b/libs/image/tests/kis_image_signal_router_test.cpp @@ -1,68 +1,71 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_signal_router_test.h" #include inline void KisImageSignalRouterTest::checkNotification(KisImageSignalType notification, const char *signal) { QSignalSpy *spy = new QSignalSpy(m_image.data(), signal); QCOMPARE(spy->count(), 0); m_image->signalRouter()->emitNotification(notification); QCOMPARE(spy->count(), 1); delete spy; } #define checkComplexSignal(method, signal) \ { \ QSignalSpy *spy = new QSignalSpy(m_image.data(), signal); \ QCOMPARE(spy->count(), 0); \ m_image->signalRouter()->method; \ QCOMPARE(spy->count(), 1); \ delete spy; \ } void KisImageSignalRouterTest::init() { initBase(); constructImage(); } void KisImageSignalRouterTest::cleanup() { cleanupBase(); } void KisImageSignalRouterTest::testSignalForwarding() { + checkNotification(LayersChangedSignal, SIGNAL(sigLayersChangedAsync())); checkNotification(ModifiedSignal, SIGNAL(sigImageModified())); checkNotification(SizeChangedSignal, SIGNAL(sigSizeChanged(QPointF,QPointF))); checkNotification(ComplexSizeChangedSignal(), SIGNAL(sigSizeChanged(QPointF,QPointF))); - checkNotification(ProfileChangedSignal, SIGNAL(sigProfileChanged(const KoColorProfile*))); - checkNotification(ColorSpaceChangedSignal, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); +// These cannot be checked because KoColorProfile and KoColorSpace are not registered metatypes, +// and cannot be registered as metatypes because they are abstract classes. +// checkNotification(ProfileChangedSignal, SIGNAL(sigProfileChanged(const KoColorProfile*))); +// checkNotification(ColorSpaceChangedSignal, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); checkNotification(ResolutionChangedSignal, SIGNAL(sigResolutionChanged(double,double))); checkComplexSignal(emitNodeChanged(m_layer1.data()), SIGNAL(sigNodeChanged(KisNodeSP))); checkComplexSignal(emitNodeHasBeenAdded(m_layer3.data(),0), SIGNAL(sigNodeAddedAsync(KisNodeSP))); checkComplexSignal(emitAboutToRemoveANode(m_layer3.data(),0), SIGNAL(sigRemoveNodeAsync(KisNodeSP))); } QTEST_MAIN(KisImageSignalRouterTest) diff --git a/libs/image/tiles3/swap/kis_tile_data_swapper.cpp b/libs/image/tiles3/swap/kis_tile_data_swapper.cpp index be8ec18a29..452dd0f499 100644 --- a/libs/image/tiles3/swap/kis_tile_data_swapper.cpp +++ b/libs/image/tiles3/swap/kis_tile_data_swapper.cpp @@ -1,242 +1,241 @@ /* * Copyright (c) 2010 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 #include "tiles3/swap/kis_tile_data_swapper.h" #include "tiles3/swap/kis_tile_data_swapper_p.h" #include "tiles3/kis_tile_data.h" #include "tiles3/kis_tile_data_store.h" #include "tiles3/kis_tile_data_store_iterators.h" #include "kis_debug.h" #define SEC 1000 const qint32 KisTileDataSwapper::TIMEOUT = -1; const qint32 KisTileDataSwapper::DELAY = 0.7 * SEC; //#define DEBUG_SWAPPER #ifdef DEBUG_SWAPPER #define DEBUG_ACTION(action) dbgKrita << action #define DEBUG_VALUE(value) dbgKrita << "\t" << ppVar(value) #else #define DEBUG_ACTION(action) #define DEBUG_VALUE(value) #endif class SoftSwapStrategy; class AggressiveSwapStrategy; struct Q_DECL_HIDDEN KisTileDataSwapper::Private { public: QSemaphore semaphore; QAtomicInt shouldExitFlag; KisTileDataStore *store; KisStoreLimits limits; QMutex cycleLock; }; KisTileDataSwapper::KisTileDataSwapper(KisTileDataStore *store) : QThread(), m_d(new Private()) { m_d->shouldExitFlag = 0; m_d->store = store; } KisTileDataSwapper::~KisTileDataSwapper() { delete m_d; } void KisTileDataSwapper::kick() { m_d->semaphore.release(); } void KisTileDataSwapper::terminateSwapper() { unsigned long exitTimeout = 100; do { m_d->shouldExitFlag = true; kick(); } while(!wait(exitTimeout)); } void KisTileDataSwapper::waitForWork() { m_d->semaphore.tryAcquire(1, TIMEOUT); } void KisTileDataSwapper::run() { while (1) { waitForWork(); if (m_d->shouldExitFlag) return; QThread::msleep(DELAY); doJob(); } } void KisTileDataSwapper::checkFreeMemory() { // dbgKrita <<"check memory: high limit -" << m_d->limits.emergencyThreshold() <<"in mem -" << m_d->store->numTilesInMemory(); if(m_d->store->memoryMetric() > m_d->limits.emergencyThreshold()) doJob(); } void KisTileDataSwapper::doJob() { /** * In emergency case usual threads have access * to this function as well */ QMutexLocker locker(&m_d->cycleLock); qint32 memoryMetric = m_d->store->memoryMetric(); DEBUG_ACTION("Started swap cycle"); DEBUG_VALUE(m_d->store->numTiles()); DEBUG_VALUE(m_d->store->numTilesInMemory()); DEBUG_VALUE(memoryMetric); DEBUG_VALUE(m_d->limits.softLimitThreshold()); DEBUG_VALUE(m_d->limits.hardLimitThreshold()); if(memoryMetric > m_d->limits.softLimitThreshold()) { qint32 softFree = memoryMetric - m_d->limits.softLimit(); DEBUG_VALUE(softFree); DEBUG_ACTION("\t pass0"); memoryMetric -= pass(softFree); DEBUG_VALUE(memoryMetric); if(memoryMetric > m_d->limits.hardLimitThreshold()) { qint32 hardFree = memoryMetric - m_d->limits.hardLimit(); DEBUG_VALUE(hardFree); DEBUG_ACTION("\t pass1"); memoryMetric -= pass(hardFree); DEBUG_VALUE(memoryMetric); } } } class SoftSwapStrategy { public: typedef KisTileDataStoreIterator iterator; static inline iterator* beginIteration(KisTileDataStore *store) { return store->beginIteration(); } static inline void endIteration(KisTileDataStore *store, iterator *iter) { store->endIteration(iter); } static inline bool isInteresting(KisTileData *td) { // We are working with mementoed tiles only... return td->historical(); } static inline bool swapOutFirst(KisTileData *td) { return td->age() > 0; } }; class AggressiveSwapStrategy { public: typedef KisTileDataStoreClockIterator iterator; static inline iterator* beginIteration(KisTileDataStore *store) { return store->beginClockIteration(); } static inline void endIteration(KisTileDataStore *store, iterator *iter) { store->endIteration(iter); } static inline bool isInteresting(KisTileData *td) { // Add some aggression... Q_UNUSED(td); return true; // >:) } static inline bool swapOutFirst(KisTileData *td) { return td->age() > 0; } }; template qint64 KisTileDataSwapper::pass(qint64 needToFreeMetric) { qint64 freedMetric = 0; QList additionalCandidates; typename strategy::iterator *iter = strategy::beginIteration(m_d->store); - KisTileData *item; + KisTileData *item = 0; - while(iter->hasNext()) { + while (iter->hasNext()) { item = iter->next(); - if(freedMetric >= needToFreeMetric) break; + if (freedMetric >= needToFreeMetric) break; + if (!strategy::isInteresting(item)) continue; - if(!strategy::isInteresting(item)) continue; - - if(strategy::swapOutFirst(item)) { - if(iter->trySwapOut(item)) { + if (strategy::swapOutFirst(item)) { + if (iter->trySwapOut(item)) { freedMetric += item->pixelSize(); } } else { item->markOld(); additionalCandidates.append(item); } } Q_FOREACH (item, additionalCandidates) { - if(freedMetric >= needToFreeMetric) break; + if (freedMetric >= needToFreeMetric) break; - if(iter->trySwapOut(item)) { + if (iter->trySwapOut(item)) { freedMetric += item->pixelSize(); } } strategy::endIteration(m_d->store, iter); return freedMetric; } void KisTileDataSwapper::testingRereadConfig() { m_d->limits = KisStoreLimits(); } diff --git a/libs/pigment/KoColorSpaceRegistry.cpp b/libs/pigment/KoColorSpaceRegistry.cpp index 85ed7a31f4..d6f3e8404b 100644 --- a/libs/pigment/KoColorSpaceRegistry.cpp +++ b/libs/pigment/KoColorSpaceRegistry.cpp @@ -1,849 +1,893 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004,2010 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include "KoPluginLoader.h" #include "KoGenericRegistry.h" #include "DebugPigment.h" #include "KoBasicHistogramProducers.h" #include "KoColorSpace.h" #include "KoColorProfile.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "colorspaces/KoAlphaColorSpace.h" #include "colorspaces/KoLabColorSpace.h" #include "colorspaces/KoRgbU16ColorSpace.h" #include "colorspaces/KoRgbU8ColorSpace.h" #include "colorspaces/KoSimpleColorSpaceEngine.h" #include "KoColorSpace_p.h" #include "kis_assert.h" #include "KoColorProfileStorage.h" #include #include Q_GLOBAL_STATIC(KoColorSpaceRegistry, s_instance) struct Q_DECL_HIDDEN KoColorSpaceRegistry::Private { // interface for KoColorSpaceFactory struct ProfileRegistrationInterface; // interface for KoColorConversionSystem struct ConversionSystemInterface; Private(KoColorSpaceRegistry *_q) : q(_q) {} KoColorSpaceRegistry *q; KoGenericRegistry colorSpaceFactoryRegistry; KoColorProfileStorage profileStorage; QHash csMap; QScopedPointer conversionSystemInterface; KoColorConversionSystem *colorConversionSystem; KoColorConversionCache* colorConversionCache; const KoColorSpace *rgbU8sRGB; const KoColorSpace *lab16sLAB; const KoColorSpace *alphaCs; const KoColorSpace *alphaU16Cs; #ifdef HAVE_OPENEXR const KoColorSpace *alphaF16Cs; #endif const KoColorSpace *alphaF32Cs; QReadWriteLock registrylock; /** * The function checks if a colorspace with a certain id and profile name can be found in the cache * NOTE: the function doesn't take any lock but it needs to be called inside a d->registryLock * locked either in read or write. * @param csId The colorspace id * @param profileName The colorspace profile name * @retval KoColorSpace The matching colorspace * @retval 0 Null pointer if not match */ const KoColorSpace* getCachedColorSpaceImpl(const QString & csId, const QString & profileName) const; QString idsToCacheName(const QString & csId, const QString & profileName) const; QString defaultProfileForCsIdImpl(const QString &csID); const KoColorProfile * profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName); QString colorSpaceIdImpl(const QString & colorModelId, const QString & colorDepthId) const; const KoColorSpace *lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile); /** * Return a colorspace that works with the parameter profile. * @param profileName the name of the KoColorProfile to be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ template const KoColorSpace * colorSpace1(const QString &colorSpaceId, const QString &pName = QString()); /** * Return a colorspace that works with the parameter profile. * @param colorSpaceId the ID string of the colorspace that you want to have returned * @param profile the profile be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace1(const QString &colorSpaceId, const KoColorProfile *profile); }; struct KoColorSpaceRegistry::Private::ConversionSystemInterface : public KoColorConversionSystem::RegistryInterface { ConversionSystemInterface(KoColorSpaceRegistry *parentRegistry) : q(parentRegistry) { } const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName) { return q->d->colorSpace1(q->d->colorSpaceIdImpl(colorModelId, colorDepthId), profileName); } const KoColorSpaceFactory* colorSpaceFactory(const QString &colorModelId, const QString &colorDepthId) const { return q->d->colorSpaceFactoryRegistry.get(q->d->colorSpaceIdImpl(colorModelId, colorDepthId)); } QList profilesFor(const KoColorSpaceFactory * csf) const { return q->d->profileStorage.profilesFor(csf); } QList colorSpacesFor(const KoColorProfile* profile) const { QList csfs; Q_FOREACH (KoColorSpaceFactory* csf, q->d->colorSpaceFactoryRegistry.values()) { if (csf->profileIsCompatible(profile)) { csfs.push_back(csf); } } return csfs; } private: KoColorSpaceRegistry *q; }; KoColorSpaceRegistry* KoColorSpaceRegistry::instance() { if (!s_instance.exists()) { s_instance->init(); } return s_instance; } void KoColorSpaceRegistry::init() { d->rgbU8sRGB = 0; d->lab16sLAB = 0; d->alphaCs = 0; d->alphaU16Cs = 0; #ifdef HAVE_OPENEXR d->alphaF16Cs = 0; #endif d->alphaF32Cs = 0; d->conversionSystemInterface.reset(new Private::ConversionSystemInterface(this)); d->colorConversionSystem = new KoColorConversionSystem(d->conversionSystemInterface.data()); d->colorConversionCache = new KoColorConversionCache; KoColorSpaceEngineRegistry::instance()->add(new KoSimpleColorSpaceEngine()); addProfile(new KoDummyColorProfile); // Create the built-in colorspaces QList localFactories; localFactories << new KoAlphaColorSpaceFactory() << new KoAlphaU16ColorSpaceFactory() #ifdef HAVE_OPENEXR << new KoAlphaF16ColorSpaceFactory() #endif << new KoAlphaF32ColorSpaceFactory() << new KoLabColorSpaceFactory() << new KoRgbU8ColorSpaceFactory() << new KoRgbU16ColorSpaceFactory(); Q_FOREACH (KoColorSpaceFactory *factory, localFactories) { add(factory); } KoPluginLoader::PluginsConfig config; config.whiteList = "ColorSpacePlugins"; config.blacklist = "ColorSpacePluginsDisabled"; config.group = "calligra"; KoPluginLoader::instance()->load("Calligra/ColorSpace", "[X-Pigment-PluginVersion] == 28", config); KoPluginLoader::PluginsConfig configExtensions; configExtensions.whiteList = "ColorSpaceExtensionsPlugins"; configExtensions.blacklist = "ColorSpaceExtensionsPluginsDisabled"; configExtensions.group = "calligra"; KoPluginLoader::instance()->load("Calligra/ColorSpaceExtension", "[X-Pigment-PluginVersion] == 28", configExtensions); dbgPigment << "Loaded the following colorspaces:"; Q_FOREACH (const KoID& id, listKeys()) { dbgPigment << "\t" << id.id() << "," << id.name(); } } KoColorSpaceRegistry::KoColorSpaceRegistry() : d(new Private(this)) { d->colorConversionSystem = 0; d->colorConversionCache = 0; } KoColorSpaceRegistry::~KoColorSpaceRegistry() { // Just leak on exit... It's faster. // delete d->colorConversionSystem; // Q_FOREACH (KoColorProfile* profile, d->profileMap) { // delete profile; // } // d->profileMap.clear(); // Q_FOREACH (const KoColorSpace * cs, d->csMap) { // cs->d->deletability = OwnedByRegistryRegistryDeletes; // } // d->csMap.clear(); // // deleting colorspaces calls a function in the cache // delete d->colorConversionCache; // d->colorConversionCache = 0; // // Delete the colorspace factories // qDeleteAll(d->localFactories); delete d; } void KoColorSpaceRegistry::add(KoColorSpaceFactory* item) { QWriteLocker l(&d->registrylock); d->colorSpaceFactoryRegistry.add(item); d->colorConversionSystem->insertColorSpace(item); } void KoColorSpaceRegistry::remove(KoColorSpaceFactory* item) { QWriteLocker l(&d->registrylock); QList toremove; Q_FOREACH (const KoColorSpace * cs, d->csMap) { if (cs->id() == item->id()) { toremove.push_back(d->idsToCacheName(cs->id(), cs->profile()->name())); cs->d->deletability = OwnedByRegistryRegistryDeletes; } } Q_FOREACH (const QString& id, toremove) { d->csMap.remove(id); // TODO: should not it delete the color space when removing it from the map ? } d->colorSpaceFactoryRegistry.remove(item->id()); } void KoColorSpaceRegistry::addProfileAlias(const QString& name, const QString& to) { d->profileStorage.addProfileAlias(name, to); } QString KoColorSpaceRegistry::profileAlias(const QString& name) const { return d->profileStorage.profileAlias(name); } const KoColorProfile* KoColorSpaceRegistry::profileByName(const QString &name) const { return d->profileStorage.profileByName(name); } const KoColorProfile * KoColorSpaceRegistry::profileByUniqueId(const QByteArray &id) const { return d->profileStorage.profileByUniqueId(id); } QList KoColorSpaceRegistry::profilesFor(const QString &csID) const { QReadLocker l(&d->registrylock); return d->profileStorage.profilesFor(d->colorSpaceFactoryRegistry.value(csID)); } const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile) { return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId), profile); } const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName) { return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId), profileName); } const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId) { return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId)); } bool KoColorSpaceRegistry::profileIsCompatible(const KoColorProfile *profile, const QString &colorSpaceId) { QReadLocker l(&d->registrylock); KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(colorSpaceId); return csf ? csf->profileIsCompatible(profile) : false; } void KoColorSpaceRegistry::addProfileToMap(KoColorProfile *p) { d->profileStorage.addProfile(p); } void KoColorSpaceRegistry::addProfile(KoColorProfile *p) { if (!p->valid()) return; QWriteLocker locker(&d->registrylock); if (p->valid()) { addProfileToMap(p); d->colorConversionSystem->insertColorProfile(p); } } void KoColorSpaceRegistry::addProfile(const KoColorProfile* profile) { addProfile(profile->clone()); } void KoColorSpaceRegistry::removeProfile(KoColorProfile* profile) { d->profileStorage.removeProfile(profile); // FIXME: how about removing it from conversion system? } const KoColorSpace* KoColorSpaceRegistry::Private::getCachedColorSpaceImpl(const QString & csID, const QString & profileName) const { auto it = csMap.find(idsToCacheName(csID, profileName)); if (it != csMap.end()) { return it.value(); } return 0; } QString KoColorSpaceRegistry::Private::idsToCacheName(const QString & csID, const QString & profileName) const { return csID + "" + profileName; } QString KoColorSpaceRegistry::defaultProfileForColorSpace(const QString &colorSpaceId) const { QReadLocker l(&d->registrylock); return d->defaultProfileForCsIdImpl(colorSpaceId); } KoColorConversionTransformation *KoColorSpaceRegistry::createColorConverter(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { QWriteLocker l(&d->registrylock); return d->colorConversionSystem->createColorConverter(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } void KoColorSpaceRegistry::createColorConverters(const KoColorSpace *colorSpace, const QList > &possibilities, KoColorConversionTransformation *&fromCS, KoColorConversionTransformation *&toCS) const { QWriteLocker l(&d->registrylock); d->colorConversionSystem->createColorConverters(colorSpace, possibilities, fromCS, toCS); } QString KoColorSpaceRegistry::Private::defaultProfileForCsIdImpl(const QString &csID) { QString defaultProfileName; KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID); if (csf) { defaultProfileName = csf->defaultProfile(); } else { dbgPigmentCSRegistry << "Unknown color space type : " << csID; } return defaultProfileName; } const KoColorProfile *KoColorSpaceRegistry::Private::profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName) { const KoColorProfile *profile = 0; // last attempt at getting a profile, sometimes the default profile, like adobe cmyk isn't available. profile = profileStorage.profileByName(profileName); if (!profile) { dbgPigmentCSRegistry << "Profile not found :" << profileName; // first try: default profile = profileStorage.profileByName(defaultProfileForCsIdImpl(csID)); if (!profile) { // second try: first profile in the list QList profiles = profileStorage.profilesFor(colorSpaceFactoryRegistry.value(csID)); if (profiles.isEmpty() || !profiles.first()) { dbgPigmentCSRegistry << "Couldn't fetch a fallback profile:" << profileName; qWarning() << "profileForCsIdWithFallbackImpl couldn't fetch a fallback profile for " << qUtf8Printable(profileName); return 0; } profile = profiles.first(); } } return profile; } const KoColorSpace *KoColorSpaceRegistry::Private::lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile) { const KoColorSpace *cs = 0; /* * We need to check again here, a thread requesting the same colorspace could've added it * already, in between the read unlock and write lock. * TODO: We also potentially changed profileName content, which means we maybe are going to * create a colorspace that's actually in the space registry cache, but currently this might * not be an issue because the colorspace should be cached also by the factory, so it won't * create a new instance. That being said, having two caches with the same stuff doesn't make * much sense. */ cs = getCachedColorSpaceImpl(csID, profile->name()); if (!cs) { KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID); cs = csf->grabColorSpace(profile); if (!cs) { dbgPigmentCSRegistry << "Unable to create color space"; qWarning() << "lazyCreateColorSpaceImpl was unable to create a color space for " << csID; return 0; } dbgPigmentCSRegistry << "colorspace count: " << csMap.count() << ", adding name: " << idsToCacheName(cs->id(), cs->profile()->name()) << "\n\tcsID" << csID << "\n\tcs->id()" << cs->id() << "\n\tcs->profile()->name()" << cs->profile()->name() << "\n\tprofile->name()" << profile->name(); Q_ASSERT(cs->id() == csID); Q_ASSERT(cs->profile()->name() == profile->name()); csMap[idsToCacheName(cs->id(), cs->profile()->name())] = cs; cs->d->deletability = OwnedByRegistryDoNotDelete; } return cs; } template const KoColorSpace * KoColorSpaceRegistry::Private::colorSpace1(const QString &csID, const QString &pName) { QString profileName = pName; const KoColorSpace *cs = 0; { typename LockPolicy::ReadLocker l(®istrylock); if (profileName.isEmpty()) { profileName = defaultProfileForCsIdImpl(csID); if (profileName.isEmpty()) return 0; } // quick attempt to fetch a cached color space cs = getCachedColorSpaceImpl(csID, profileName); } if (!cs) { // slow attempt to create a color space typename LockPolicy::WriteLocker l(®istrylock); const KoColorProfile *profile = profileForCsIdWithFallbackImpl(csID, profileName); - // until kis_asert.h is not available in 3.1, use this combo - Q_ASSERT(profile); if (!profile) return 0; cs = lazyCreateColorSpaceImpl(csID, profile); } else { KIS_SAFE_ASSERT_RECOVER_NOOP(cs->id() == csID); KIS_SAFE_ASSERT_RECOVER_NOOP(cs->profile()->name() == profileName); } return cs; } const KoColorSpace * KoColorSpaceRegistry::Private::colorSpace1(const QString &csID, const KoColorProfile *profile) { if (csID.isEmpty()) { return 0; } else if (!profile) { return colorSpace1(csID); } const KoColorSpace *cs = 0; { QReadLocker l(®istrylock); cs = getCachedColorSpaceImpl(csID, profile->name()); } // the profile should have already been added to the registry by createColorProfile() method KIS_SAFE_ASSERT_RECOVER(profileStorage.containsProfile(profile)) { // warning! locking happens inside addProfile! q->addProfile(profile); } if (!cs) { // The profile was not stored and thus not the combination either QWriteLocker l(®istrylock); KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID); if (!csf) { dbgPigmentCSRegistry << "Unknown color space type :" << csf; return 0; } if (!csf->profileIsCompatible(profile)) { dbgPigmentCSRegistry << "Profile is not compatible:" << csf << profile->name(); return 0; } cs = lazyCreateColorSpaceImpl(csID, profile); } return cs; } const KoColorSpace * KoColorSpaceRegistry::alpha8() { if (!d->alphaCs) { d->alphaCs = d->colorSpace1(KoAlphaColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaCs); return d->alphaCs; } const KoColorSpace * KoColorSpaceRegistry::alpha16() { if (!d->alphaU16Cs) { d->alphaU16Cs = d->colorSpace1(KoAlphaU16ColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaU16Cs); return d->alphaU16Cs; } #ifdef HAVE_OPENEXR const KoColorSpace * KoColorSpaceRegistry::alpha16f() { if (!d->alphaF16Cs) { d->alphaF16Cs = d->colorSpace1(KoAlphaF16ColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaF16Cs); return d->alphaF16Cs; } #endif const KoColorSpace * KoColorSpaceRegistry::alpha32f() { if (!d->alphaF32Cs) { d->alphaF32Cs = d->colorSpace1(KoAlphaF32ColorSpace::colorSpaceId()); } Q_ASSERT(d->alphaF32Cs); return d->alphaF32Cs; } +const KoColorSpace *KoColorSpaceRegistry::graya8(const QString &profile) +{ + + if (profile.isEmpty()) { + KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(GrayAColorModelID.id()); + return KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), factory->defaultProfile()); + } + else { + return KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + } + +} + +const KoColorSpace *KoColorSpaceRegistry::graya8(const KoColorProfile *profile) +{ + if (!profile) { + return graya8(); + } + else { + return KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + } + +} + +const KoColorSpace *KoColorSpaceRegistry::graya16(const QString &profile) +{ + if (profile.isEmpty()) { + KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(GrayAColorModelID.id()); + return KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), factory->defaultProfile()); + } + else { + return KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); + } + +} + +const KoColorSpace *KoColorSpaceRegistry::graya16(const KoColorProfile *profile) +{ + if (!profile) { + return graya8(); + } + else { + return KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); + } +} + const KoColorSpace * KoColorSpaceRegistry::rgb8(const QString &profileName) { if (profileName.isEmpty()) { if (!d->rgbU8sRGB) { d->rgbU8sRGB = d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId()); } Q_ASSERT(d->rgbU8sRGB); return d->rgbU8sRGB; } return d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId(), profileName); } const KoColorSpace * KoColorSpaceRegistry::rgb8(const KoColorProfile * profile) { if (profile == 0) { if (!d->rgbU8sRGB) { d->rgbU8sRGB = d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId()); } Q_ASSERT(d->rgbU8sRGB); return d->rgbU8sRGB; } return d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId(), profile); } const KoColorSpace * KoColorSpaceRegistry::rgb16(const QString &profileName) { return d->colorSpace1(KoRgbU16ColorSpace::colorSpaceId(), profileName); } const KoColorSpace * KoColorSpaceRegistry::rgb16(const KoColorProfile * profile) { return d->colorSpace1(KoRgbU16ColorSpace::colorSpaceId(), profile); } const KoColorSpace * KoColorSpaceRegistry::lab16(const QString &profileName) { if (profileName.isEmpty()) { if (!d->lab16sLAB) { d->lab16sLAB = d->colorSpace1(KoLabColorSpace::colorSpaceId()); } return d->lab16sLAB; } return d->colorSpace1(KoLabColorSpace::colorSpaceId(), profileName); } const KoColorSpace * KoColorSpaceRegistry::lab16(const KoColorProfile * profile) { if (profile == 0) { if (!d->lab16sLAB) { d->lab16sLAB = d->colorSpace1(KoLabColorSpace::colorSpaceId()); } Q_ASSERT(d->lab16sLAB); return d->lab16sLAB; } return d->colorSpace1(KoLabColorSpace::colorSpaceId(), profile); } const KoColorProfile *KoColorSpaceRegistry::p2020G10Profile() const { return profileByName("Rec2020-elle-V4-g10.icc"); } const KoColorProfile *KoColorSpaceRegistry::p2020PQProfile() const { return profileByName("High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF"); } const KoColorProfile *KoColorSpaceRegistry::p709G10Profile() const { return profileByName("sRGB-elle-V2-g10.icc"); } const KoColorProfile *KoColorSpaceRegistry::p709SRGBProfile() const { return profileByName("sRGB-elle-V2-srgbtrc.icc"); } QList KoColorSpaceRegistry::colorModelsList(ColorSpaceListVisibility option) const { QReadLocker l(&d->registrylock); QList ids; QList factories = d->colorSpaceFactoryRegistry.values(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { if (!ids.contains(factory->colorModelId()) && (option == AllColorSpaces || factory->userVisible())) { ids << factory->colorModelId(); } } return ids; } QList KoColorSpaceRegistry::colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const { return colorDepthList(colorModelId.id(), option); } QList KoColorSpaceRegistry::colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const { QReadLocker l(&d->registrylock); QList ids; QList factories = d->colorSpaceFactoryRegistry.values(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { if (!ids.contains(KoID(factory->colorDepthId())) && factory->colorModelId().id() == colorModelId && (option == AllColorSpaces || factory->userVisible())) { ids << factory->colorDepthId(); } } QList r; if (ids.contains(Integer8BitsColorDepthID)) r << Integer8BitsColorDepthID; if (ids.contains(Integer16BitsColorDepthID)) r << Integer16BitsColorDepthID; if (ids.contains(Float16BitsColorDepthID)) r << Float16BitsColorDepthID; if (ids.contains(Float32BitsColorDepthID)) r << Float32BitsColorDepthID; if (ids.contains(Float64BitsColorDepthID)) r << Float64BitsColorDepthID; return r; } QString KoColorSpaceRegistry::Private::colorSpaceIdImpl(const QString & colorModelId, const QString & colorDepthId) const { QList factories = colorSpaceFactoryRegistry.values(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { if (factory->colorModelId().id() == colorModelId && factory->colorDepthId().id() == colorDepthId) { return factory->id(); } } return ""; } QString KoColorSpaceRegistry::colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const { QReadLocker l(&d->registrylock); return d->colorSpaceIdImpl(colorModelId, colorDepthId); } QString KoColorSpaceRegistry::colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const { return colorSpaceId(colorModelId.id(), colorDepthId.id()); } KoID KoColorSpaceRegistry::colorSpaceColorModelId(const QString & _colorSpaceId) const { QReadLocker l(&d->registrylock); KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId); if (factory) { return factory->colorModelId(); } else { return KoID(); } } KoID KoColorSpaceRegistry::colorSpaceColorDepthId(const QString & _colorSpaceId) const { QReadLocker l(&d->registrylock); KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId); if (factory) { return factory->colorDepthId(); } else { return KoID(); } } const KoColorConversionSystem* KoColorSpaceRegistry::colorConversionSystem() const { return d->colorConversionSystem; } KoColorConversionCache* KoColorSpaceRegistry::colorConversionCache() const { return d->colorConversionCache; } const KoColorSpace* KoColorSpaceRegistry::permanentColorspace(const KoColorSpace* _colorSpace) { if (_colorSpace->d->deletability != NotOwnedByRegistry) { return _colorSpace; } else if (*_colorSpace == *d->alphaCs) { return d->alphaCs; } else { const KoColorSpace* cs = d->colorSpace1(_colorSpace->id(), _colorSpace->profile()); Q_ASSERT(cs); Q_ASSERT(*cs == *_colorSpace); return cs; } } QList KoColorSpaceRegistry::listKeys() const { QReadLocker l(&d->registrylock); QList answer; Q_FOREACH (const QString& key, d->colorSpaceFactoryRegistry.keys()) { answer.append(KoID(key, d->colorSpaceFactoryRegistry.get(key)->name())); } return answer; } struct KoColorSpaceRegistry::Private::ProfileRegistrationInterface : public KoColorSpaceFactory::ProfileRegistrationInterface { ProfileRegistrationInterface(KoColorSpaceRegistry::Private *_d) : d(_d) {} const KoColorProfile* profileByName(const QString &profileName) const override { return d->profileStorage.profileByName(profileName); } void registerNewProfile(KoColorProfile *profile) override { d->profileStorage.addProfile(profile); d->colorConversionSystem->insertColorProfile(profile); } KoColorSpaceRegistry::Private *d; }; const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString& colorModelId, const QString& colorDepthId, const QByteArray& rawData) { QWriteLocker l(&d->registrylock); KoColorSpaceFactory* factory_ = d->colorSpaceFactoryRegistry.get(d->colorSpaceIdImpl(colorModelId, colorDepthId)); Private::ProfileRegistrationInterface interface(d); return factory_->colorProfile(rawData, &interface); } QList KoColorSpaceRegistry::allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection) { QList colorSpaces; // TODO: thread-unsafe code: the factories might change right after the lock in released // HINT: used in a unittest only! d->registrylock.lockForRead(); QList factories = d->colorSpaceFactoryRegistry.values(); d->registrylock.unlock(); Q_FOREACH (KoColorSpaceFactory* factory, factories) { // Don't test with ycbcr for now, since we don't have a default profile for it. if (factory->colorModelId().id().startsWith("Y")) continue; if (visibility == AllColorSpaces || factory->userVisible()) { if (pSelection == OnlyDefaultProfile) { const KoColorSpace *cs = d->colorSpace1(factory->id()); if (cs) { colorSpaces.append(cs); } else { warnPigment << "Could not create colorspace for id" << factory->id() << "since there is no working default profile"; } } else { QList profiles = KoColorSpaceRegistry::instance()->profilesFor(factory->id()); Q_FOREACH (const KoColorProfile * profile, profiles) { const KoColorSpace *cs = d->colorSpace1(factory->id(), profile); if (cs) { colorSpaces.append(cs); } else { warnPigment << "Could not create colorspace for id" << factory->id() << "and profile" << profile->name(); } } } } } return colorSpaces; } diff --git a/libs/pigment/KoColorSpaceRegistry.h b/libs/pigment/KoColorSpaceRegistry.h index 0f4b71d0bb..8a16c5a091 100644 --- a/libs/pigment/KoColorSpaceRegistry.h +++ b/libs/pigment/KoColorSpaceRegistry.h @@ -1,384 +1,417 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004,2010 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACEREGISTRY_H #define KOCOLORSPACEREGISTRY_H #include #include #include #include "kritapigment_export.h" #include #include #include #include class KoColorProfile; class KoColorConversionSystem; class KoColorConversionCache; class KoColorConversionTransformation; /** * The registry for colorspaces and profiles. * This class contains: * - a registry of colorspace instantiated with specific profiles. * - a registry of singleton colorspace factories. * - a registry of icc profiles * * Locking policy details: * * Basically, we have two levels of locks in the registry: * 1) (outer level) is Private::registrylock, which controls the structures * of the color space registry itself * 2) (inner level) is KoColorProfileStorage::Private::lock controls * the structures related to profiles. * * The locks can be taken individually, but if you are going to take both * of them, you should always follow the order 1) registry; 2) profiles. * Otherwise you'll get a deadlock. * * To avoid recursive deadlocks, all the dependent classes * (KoColorConversionSystem and KoColorSpaceFactory) now do not use the direct * links to the registry. Instead, they use special private interfaces that * skip recursive locking and ensure we take a lock twice. */ class KRITAPIGMENT_EXPORT KoColorSpaceRegistry { public: KoColorSpaceRegistry(); enum ColorSpaceListVisibility { OnlyUserVisible = 1, ///< Only user visible color space AllColorSpaces = 4 ///< All color space even those not visible to the user }; enum ColorSpaceListProfilesSelection { OnlyDefaultProfile = 1, ///< Only add the default profile AllProfiles = 4 ///< Add all profiles }; /** * Return an instance of the KoColorSpaceRegistry * Creates an instance if that has never happened before and returns the singleton instance. */ static KoColorSpaceRegistry * instance(); virtual ~KoColorSpaceRegistry(); public: /** * add a color space to the registry * @param item the color space factory to add */ void add(KoColorSpaceFactory* item); /** * Remove a color space factory from the registry. Note that it is the * responsibility of the caller to ensure that the colorspaces are not * used anymore. */ void remove(KoColorSpaceFactory* item); /** * Add a profile to the profile map but do not add it to the * color conversion system yet. * @param profile the new profile to be registered. */ void addProfileToMap(KoColorProfile *p); /** * register the profile with the color space registry * @param profile the new profile to be registered so it can be combined with * colorspaces. */ void addProfile(KoColorProfile* profile); void addProfile(const KoColorProfile* profile); // TODO why ? void removeProfile(KoColorProfile* profile); /** * Create an alias to a profile with a different name. Then @ref profileByName * will return the profile @p to when passed @p name as a parameter. */ void addProfileAlias(const QString& name, const QString& to); /** * @return the profile alias, or name if not aliased */ QString profileAlias(const QString& name) const; /** * create a profile of the specified type. */ const KoColorProfile *createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData); /** * Return a profile by its given name, or 0 if none registered. * @return a profile by its given name, or 0 if none registered. * @param name the product name as set on the profile. * @see addProfile() * @see KoColorProfile::productName() */ const KoColorProfile * profileByName(const QString & name) const ; /** * Returns a profile by its unique id stored/calculated in the header. * The first call to this function might take long, because the map is * created on the first use only (atm used by SVG only) * @param id unique ProfileID of the profile (MD5 sum of its header) * @return the profile or 0 if not found */ const KoColorProfile *profileByUniqueId(const QByteArray &id) const; bool profileIsCompatible(const KoColorProfile* profile, const QString &colorSpaceId); /** * Return the list of profiles for a colorspace with the argument id. * Profiles will not work with any color space, you can query which profiles * that are registered with this registry can be used in combination with the * argument factory. * @param colorSpaceId the colorspace-id with which all the returned profiles will work. * @return a list of profiles for the factory */ QList profilesFor(const QString& csID) const; QString defaultProfileForColorSpace(const QString &colorSpaceId) const; /** * This function is called by the color space to create a color conversion * between two color space. This function search in the graph of transformations * the best possible path between the two color space. */ KoColorConversionTransformation* createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * This function creates two transformations, one from the color space and one to the * color space. The destination color space is picked from a list of color space, such * as the conversion between the two color space is of the best quality. * * The typical use case of this function is for KoColorTransformationFactory which * doesn't support all color spaces, so unsupported color space have to find an * acceptable conversion in order to use that KoColorTransformationFactory. * * @param colorSpace the source color space * @param possibilities a list of color space among which we need to find the best * conversion * @param fromCS the conversion from the source color space will be affected to this * variable * @param toCS the revert conversion to the source color space will be affected to this * variable */ void createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const; /** * Return a colorspace that works with the parameter profile. * @param colorSpaceId the ID string of the colorspace that you want to have returned * @param profile the profile be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile); /** * Return a colorspace that works with the parameter profile. * @param profileName the name of the KoColorProfile to be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName); const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId); /** * Return the id of the colorspace that have the defined colorModelId with colorDepthId. * @param colorModelId id of the color model * @param colorDepthId id of the color depth * @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids */ QString colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const; /** * It's a convenient function that behave like the above. * Return the id of the colorspace that have the defined colorModelId with colorDepthId. * @param colorModelId id of the color model * @param colorDepthId id of the color depth * @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids */ QString colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const; /** * @return the identifier of the color model for the given color space id. * * This function is a compatibility function used to get the color space from * all kra files. */ KoID colorSpaceColorModelId(const QString & _colorSpaceId) const; /** * @return the identifier of the color depth for the given color space id. * * This function is a compatibility function used to get the color space from * all kra files. */ KoID colorSpaceColorDepthId(const QString & _colorSpaceId) const; /** * Convenience methods to get the often used alpha colorspaces */ const KoColorSpace *alpha8(); const KoColorSpace *alpha16(); #ifdef HAVE_OPENEXR const KoColorSpace *alpha16f(); #endif const KoColorSpace *alpha32f(); + /** + * Convenience method to get a GRAYA 8 bit colorspace. If a profile is not specified, + * the default profile defined in the GRAY colorspace will be used + * @param profile the profile name + * @return an 8 bit graya colorspace with the default profile or 0. + */ + const KoColorSpace *graya8(const QString &profile = QString()); + + /** + * Convenience method to get a GRAYA 8 bit colorspace. If a profile is not specified, + * the default profile defined in the GRAY colorspace will be used + * @param profile the profile + * @return an 8 bit graya colorspace with the default profile or 0. + */ + const KoColorSpace *graya8(const KoColorProfile *profile); + + /** + * Convenience method to get a GRAYA 16 bit colorspace. If a profile is not specified, + * the default profile defined in the GRAY colorspace will be used + * @param the profile + * @return an 8 bit graya colorspace with the default profile or 0. + */ + const KoColorSpace *graya16(const QString &profile = QString()); + + /** + * Convenience method to get a GRAYA 16 bit colorspace. If a profile is not specified, + * the default profile defined in the GRAY colorspace will be used + * @param the profile + * @return an 8 bit graya colorspace with the default profile or 0. + */ + const KoColorSpace *graya16(const KoColorProfile *profile); + + /** * Convenience method to get an RGBA 8bit colorspace. If a profile is not specified, * an sRGB profile will be used. * @param profileName the name of an RGB color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb8(const QString &profileName = QString()); /** * Convenience method to get an RGBA 8bit colorspace with the given profile. * @param profile an RGB profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb8(const KoColorProfile * profile); /** * Convenience method to get an RGBA 16bit colorspace. If a profile is not specified, * an sRGB profile will be used. * @param profileName the name of an RGB color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb16(const QString &profileName = QString()); /** * Convenience method to get an RGBA 16bit colorspace with the given profile. * @param profile an RGB profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb16(const KoColorProfile * profile); /** * Convenience method to get an Lab 16bit colorspace. If a profile is not specified, * an Lab profile with a D50 whitepoint will be used. * @param profileName the name of an Lab color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * lab16(const QString &profileName = QString()); /** * Convenience method to get an Lab 16bit colorspace with the given profile. * @param profile an Lab profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * lab16(const KoColorProfile * profile); /** * Convenience method to get a standard profile for Rec.2020 linear light * color space */ const KoColorProfile *p2020G10Profile() const; /** * Convenience method to get a standard profile for Rec.2020 PQ color space */ const KoColorProfile *p2020PQProfile() const; /** * Convenience method to get a standard profile for Rec. 709 linear light * color space */ const KoColorProfile *p709G10Profile() const; /** * Convenience method to get a standard profile for Rec. 709 sRGB-tone- * response-curve profile */ const KoColorProfile *p709SRGBProfile() const; /** * @return the list of available color models */ QList colorModelsList(ColorSpaceListVisibility option) const; /** * @return the list of available color models for the given colorModelId */ QList colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const; /** * @return the list of available color models for the given colorModelId */ QList colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const; /** * @return the cache of color conversion transformation to be use by KoColorSpace */ KoColorConversionCache* colorConversionCache() const; /** * @return a permanent colorspace owned by the registry, of the same type and profile * as the one given in argument */ const KoColorSpace* permanentColorspace(const KoColorSpace* _colorSpace); /** * This function return a list of all the keys in KoID format by using the name() method * on the objects stored in the registry. */ QList listKeys() const; private: friend class KisCsConversionTest; friend class KisIteratorTest; friend class KisIteratorNGTest; friend class KisPainterTest; friend class KisCrashFilterTest; friend class KoColorSpacesBenchmark; friend class TestKoColorSpaceSanity; friend class TestColorConversionSystem; friend struct FriendOfColorSpaceRegistry; /** * @return a list with an instance of all color space with their default profile. */ QList allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection); /** * @return the color conversion system use by the registry and the color * spaces to create color conversion transformation. * * WARNING: conversion system is guarded by the registry locks, don't * use it anywhere other than unittests! */ const KoColorConversionSystem* colorConversionSystem() const; private: KoColorSpaceRegistry(const KoColorSpaceRegistry&); KoColorSpaceRegistry operator=(const KoColorSpaceRegistry&); void init(); private: struct Private; Private * const d; }; #endif // KOCOLORSPACEREGISTRY_H diff --git a/libs/pigment/KoCompositeOpRegistry.cpp b/libs/pigment/KoCompositeOpRegistry.cpp index d2a4489a83..6589bc6aa5 100644 --- a/libs/pigment/KoCompositeOpRegistry.cpp +++ b/libs/pigment/KoCompositeOpRegistry.cpp @@ -1,267 +1,268 @@ /* * Copyright (c) 2005 Adrian Page * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCompositeOpRegistry.h" #include #include #include #include #include "KoCompositeOp.h" #include "KoColorSpace.h" Q_GLOBAL_STATIC(KoCompositeOpRegistry, registry) KoCompositeOpRegistry::KoCompositeOpRegistry() { m_categories << KoID("arithmetic", i18n("Arithmetic")) << KoID("binary" , i18n("Binary")) << KoID("dark" , i18n("Darken")) << KoID("light" , i18n("Lighten")) << KoID("modulo" , i18n("Modulo")) << KoID("negative" , i18n("Negative")) << KoID("mix" , i18n("Mix")) << KoID("misc" , i18n("Misc")) << KoID("hsy" , i18n("HSY")) << KoID("hsi" , i18n("HSI")) << KoID("hsl" , i18n("HSL")) << KoID("hsv" , i18n("HSV")) << KoID("quadratic" , i18n("Quadratic")); m_map.insert(m_categories[0], KoID(COMPOSITE_ADD , i18n("Addition"))); m_map.insert(m_categories[0], KoID(COMPOSITE_SUBTRACT , i18n("Subtract"))); m_map.insert(m_categories[0], KoID(COMPOSITE_MULT , i18n("Multiply"))); m_map.insert(m_categories[0], KoID(COMPOSITE_DIVIDE , i18n("Divide"))); m_map.insert(m_categories[0], KoID(COMPOSITE_INVERSE_SUBTRACT, i18n("Inverse Subtract"))); m_map.insert(m_categories[1], KoID(COMPOSITE_XOR , i18n("XOR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_OR , i18n("OR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_AND , i18n("AND"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NAND , i18n("NAND"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NOR , i18n("NOR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_XNOR , i18n("XNOR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_IMPLICATION , i18n("IMPLICATION"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NOT_IMPLICATION , i18n("NOT IMPLICATION"))); m_map.insert(m_categories[1], KoID(COMPOSITE_CONVERSE , i18n("CONVERSE"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NOT_CONVERSE , i18n("NOT CONVERSE"))); m_map.insert(m_categories[2], KoID(COMPOSITE_BURN , i18n("Burn"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_BURN, i18n("Linear Burn"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DARKEN , i18n("Darken"))); m_map.insert(m_categories[2], KoID(COMPOSITE_GAMMA_DARK , i18n("Gamma Dark"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DARKER_COLOR , i18n("Darker Color"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SHADE_IFS_ILLUSIONS, i18n("Shade (IFS Illusions)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_FOG_DARKEN_IFS_ILLUSIONS, i18n("Fog Darken (IFS Illusions)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_EASY_BURN , i18n("Easy Burn"))); m_map.insert(m_categories[3], KoID(COMPOSITE_DODGE , i18n("Color Dodge"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LIGHTEN , i18n("Lighten"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SCREEN , i18n("Screen"))); m_map.insert(m_categories[3], KoID(COMPOSITE_PIN_LIGHT , i18n("Pin Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_VIVID_LIGHT , i18n("Vivid Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_FLAT_LIGHT , i18n("Flat Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_HARD_LIGHT , i18n("Hard Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_IFS_ILLUSIONS, i18n("Soft Light (IFS Illusions)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_PEGTOP_DELPHI, i18n("Soft Light (Pegtop-Delphi)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_GAMMA_ILLUMINATION , i18n("Gamma Illumination"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LIGHTER_COLOR , i18n("Lighter Color"))); m_map.insert(m_categories[3], KoID(COMPOSITE_PNORM_A , i18n("P-Norm A"))); m_map.insert(m_categories[3], KoID(COMPOSITE_PNORM_B , i18n("P-Norm B"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SUPER_LIGHT , i18n("Super Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_TINT_IFS_ILLUSIONS, i18n("Tint (IFS Illusions)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_FOG_LIGHTEN_IFS_ILLUSIONS, i18n("Fog Lighten (IFS Illusions)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EASY_DODGE , i18n("Easy Dodge"))); + m_map.insert(m_categories[3], KoID(COMPOSITE_LUMINOSITY_SAI , i18n("Luminosity/Shine (SAI)"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MOD , i18n("Modulo"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MOD_CON , i18n("Modulo - Continuous"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DIVISIVE_MOD , i18n("Divisive Modulo"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DIVISIVE_MOD_CON , i18n("Divisive Modulo - Continuous"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MODULO_SHIFT , i18n("Modulo Shift"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MODULO_SHIFT_CON , i18n("Modulo Shift - Continuous"))); m_map.insert(m_categories[5], KoID(COMPOSITE_DIFF , i18n("Difference"))); m_map.insert(m_categories[5], KoID(COMPOSITE_EQUIVALENCE , i18n("Equivalence"))); m_map.insert(m_categories[5], KoID(COMPOSITE_ADDITIVE_SUBTRACTIVE , i18n("Additive Subtractive"))); m_map.insert(m_categories[5], KoID(COMPOSITE_EXCLUSION , i18n("Exclusion"))); m_map.insert(m_categories[5], KoID(COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent"))); m_map.insert(m_categories[5], KoID(COMPOSITE_NEGATION , i18n("Negation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_OVER , i18n("Normal"))); m_map.insert(m_categories[6], KoID(COMPOSITE_BEHIND , i18n("Behind"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GREATER , i18n("Greater"))); m_map.insert(m_categories[6], KoID(COMPOSITE_OVERLAY , i18n("Overlay"))); m_map.insert(m_categories[6], KoID(COMPOSITE_ERASE , i18n("Erase"))); m_map.insert(m_categories[6], KoID(COMPOSITE_ALPHA_DARKEN , i18n("Alpha Darken"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HARD_MIX , i18n("Hard Mix"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HARD_MIX_PHOTOSHOP, i18n("Hard Mix (Photoshop)"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GRAIN_MERGE , i18n("Grain Merge"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PARALLEL , i18n("Parallel"))); m_map.insert(m_categories[6], KoID(COMPOSITE_ALLANON , i18n("Allanon"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GEOMETRIC_MEAN , i18n("Geometric Mean"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DESTINATION_ATOP, i18n("Destination Atop"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DESTINATION_IN , i18n("Destination In"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INTERPOLATION , i18n("Interpolation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INTERPOLATIONB , i18n("Interpolation - 2X"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAA , i18n("Penumbra A"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAB , i18n("Penumbra B"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAC , i18n("Penumbra C"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAD , i18n("Penumbra D"))); m_map.insert(m_categories[7], KoID(COMPOSITE_BUMPMAP , i18n("Bumpmap"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Map"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DISSOLVE , i18n("Dissolve"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY_RED , i18n("Copy Red"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY_GREEN, i18n("Copy Green"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY_BLUE , i18n("Copy Blue"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY , i18n("Copy"))); m_map.insert(m_categories[7], KoID(COMPOSITE_TANGENT_NORMALMAP, i18n("Tangent Normalmap"))); m_map.insert(m_categories[8], KoID(COMPOSITE_COLOR , i18n("Color"))); m_map.insert(m_categories[8], KoID(COMPOSITE_HUE , i18n("Hue"))); m_map.insert(m_categories[8], KoID(COMPOSITE_SATURATION , i18n("Saturation"))); m_map.insert(m_categories[8], KoID(COMPOSITE_LUMINIZE , i18n("Luminosity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_SATURATION, i18n("Increase Saturation"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"))); m_map.insert(m_categories[9], KoID(COMPOSITE_COLOR_HSI , i18n("Color HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_HUE_HSI , i18n("Hue HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_SATURATION_HSI , i18n("Saturation HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INTENSITY , i18n("Intensity"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_INTENSITY , i18n("Increase Intensity"))); m_map.insert(m_categories[10], KoID(COMPOSITE_COLOR_HSL , i18n("Color HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_HUE_HSL , i18n("Hue HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_SATURATION_HSL , i18n("Saturation HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_LIGHTNESS , i18n("Lightness"))); m_map.insert(m_categories[10], KoID(COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness"))); m_map.insert(m_categories[10], KoID(COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness"))); m_map.insert(m_categories[11], KoID(COMPOSITE_COLOR_HSV , i18n("Color HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_HUE_HSV , i18n("Hue HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_SATURATION_HSV , i18n("Saturation HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_VALUE , i18nc("HSV Value", "Value"))); m_map.insert(m_categories[11], KoID(COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_DEC_VALUE , i18n("Decrease Value"))); m_map.insert(m_categories[11], KoID(COMPOSITE_INC_VALUE , i18n("Increase Value"))); m_map.insert(m_categories[12], KoID(COMPOSITE_REFLECT , i18n("Reflect"))); m_map.insert(m_categories[12], KoID(COMPOSITE_GLOW , i18n("Glow"))); m_map.insert(m_categories[12], KoID(COMPOSITE_FREEZE , i18n("Freeze"))); m_map.insert(m_categories[12], KoID(COMPOSITE_HEAT , i18n("Heat"))); m_map.insert(m_categories[12], KoID(COMPOSITE_GLEAT , i18n("Glow-Heat"))); m_map.insert(m_categories[12], KoID(COMPOSITE_HELOW , i18n("Heat-Glow"))); m_map.insert(m_categories[12], KoID(COMPOSITE_REEZE , i18n("Reflect-Freeze"))); m_map.insert(m_categories[12], KoID(COMPOSITE_FRECT , i18n("Freeze-Reflect"))); m_map.insert(m_categories[12], KoID(COMPOSITE_FHYRD , i18n("Heat-Glow & Freeze-Reflect Hybrid"))); } const KoCompositeOpRegistry& KoCompositeOpRegistry::instance() { return *registry; } KoID KoCompositeOpRegistry::getDefaultCompositeOp() const { return KoID(COMPOSITE_OVER, i18n("Normal")); } KoID KoCompositeOpRegistry::getKoID(const QString& compositeOpID) const { KoIDMap::const_iterator itr = std::find(m_map.begin(), m_map.end(), KoID(compositeOpID)); return (itr != m_map.end()) ? *itr : KoID(); } KoCompositeOpRegistry::KoIDMap KoCompositeOpRegistry::getCompositeOps() const { return m_map; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCategories() const { return m_categories; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoID& category, const KoColorSpace* colorSpace) const { qint32 num = m_map.count(category); KoIDMap::const_iterator beg = m_map.find(category); KoIDMap::const_iterator end = beg + num; KoIDList list; list.reserve(num); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoColorSpace* colorSpace) const { KoIDMap::const_iterator beg = m_map.begin(); KoIDMap::const_iterator end = m_map.end(); KoIDList list; list.reserve(m_map.size()); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } bool KoCompositeOpRegistry::colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const { return colorSpace ? colorSpace->hasCompositeOp(compositeOp.id()) : false; } diff --git a/libs/pigment/KoCompositeOpRegistry.h b/libs/pigment/KoCompositeOpRegistry.h index 7c0bfd5ece..b71e61470f 100644 --- a/libs/pigment/KoCompositeOpRegistry.h +++ b/libs/pigment/KoCompositeOpRegistry.h @@ -1,225 +1,228 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2011 Silvio Heinrich * * 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 KOCOMPOSITEOPREGISTRY_H #define KOCOMPOSITEOPREGISTRY_H #include #include #include #include #include "kritapigment_export.h" class KoColorSpace; #include // TODO : convert this data blob into a modern design with an enum class. // This will reduce the need for runtime string comparisons. const QString COMPOSITE_OVER = "normal"; const QString COMPOSITE_ERASE = "erase"; const QString COMPOSITE_IN = "in"; const QString COMPOSITE_OUT = "out"; const QString COMPOSITE_ALPHA_DARKEN = "alphadarken"; const QString COMPOSITE_DESTINATION_IN = "destination-in"; const QString COMPOSITE_DESTINATION_ATOP = "destination-atop"; const QString COMPOSITE_XOR = "xor"; const QString COMPOSITE_OR = "or"; const QString COMPOSITE_AND = "and"; const QString COMPOSITE_NAND = "nand"; const QString COMPOSITE_NOR = "nor"; const QString COMPOSITE_XNOR = "xnor"; const QString COMPOSITE_IMPLICATION = "implication"; const QString COMPOSITE_NOT_IMPLICATION = "not_implication"; const QString COMPOSITE_CONVERSE = "converse"; const QString COMPOSITE_NOT_CONVERSE = "not_converse"; const QString COMPOSITE_PLUS = "plus"; const QString COMPOSITE_MINUS = "minus"; const QString COMPOSITE_ADD = "add"; const QString COMPOSITE_SUBTRACT = "subtract"; const QString COMPOSITE_INVERSE_SUBTRACT = "inverse_subtract"; const QString COMPOSITE_DIFF = "diff"; const QString COMPOSITE_MULT = "multiply"; const QString COMPOSITE_DIVIDE = "divide"; const QString COMPOSITE_ARC_TANGENT = "arc_tangent"; const QString COMPOSITE_GEOMETRIC_MEAN = "geometric_mean"; const QString COMPOSITE_ADDITIVE_SUBTRACTIVE = "additive_subtractive"; const QString COMPOSITE_NEGATION = "negation"; const QString COMPOSITE_MOD = "modulo"; const QString COMPOSITE_MOD_CON = "modulo_continuous"; const QString COMPOSITE_DIVISIVE_MOD = "divisive_modulo"; const QString COMPOSITE_DIVISIVE_MOD_CON = "divisive_modulo_continuous"; const QString COMPOSITE_MODULO_SHIFT = "modulo_shift"; const QString COMPOSITE_MODULO_SHIFT_CON = "modulo_shift_continuous"; const QString COMPOSITE_EQUIVALENCE = "equivalence"; const QString COMPOSITE_ALLANON = "allanon"; const QString COMPOSITE_PARALLEL = "parallel"; const QString COMPOSITE_GRAIN_MERGE = "grain_merge"; const QString COMPOSITE_GRAIN_EXTRACT = "grain_extract"; const QString COMPOSITE_EXCLUSION = "exclusion"; const QString COMPOSITE_HARD_MIX = "hard mix"; const QString COMPOSITE_HARD_MIX_PHOTOSHOP = "hard_mix_photoshop"; const QString COMPOSITE_OVERLAY = "overlay"; const QString COMPOSITE_BEHIND = "behind"; const QString COMPOSITE_GREATER = "greater"; const QString COMPOSITE_HARD_OVERLAY = "hard overlay"; const QString COMPOSITE_INTERPOLATION = "interpolation"; const QString COMPOSITE_INTERPOLATIONB = "interpolation 2x"; const QString COMPOSITE_PENUMBRAA = "penumbra a"; const QString COMPOSITE_PENUMBRAB = "penumbra b"; const QString COMPOSITE_PENUMBRAC = "penumbra c"; const QString COMPOSITE_PENUMBRAD = "penumbra d"; const QString COMPOSITE_DARKEN = "darken"; const QString COMPOSITE_BURN = "burn";//this is also known as 'color burn'. const QString COMPOSITE_LINEAR_BURN = "linear_burn"; const QString COMPOSITE_GAMMA_DARK = "gamma_dark"; const QString COMPOSITE_SHADE_IFS_ILLUSIONS = "shade_ifs_illusions"; const QString COMPOSITE_FOG_DARKEN_IFS_ILLUSIONS = "fog_darken_ifs_illusions"; const QString COMPOSITE_EASY_BURN = "easy burn"; const QString COMPOSITE_LIGHTEN = "lighten"; const QString COMPOSITE_DODGE = "dodge"; const QString COMPOSITE_LINEAR_DODGE = "linear_dodge"; const QString COMPOSITE_SCREEN = "screen"; const QString COMPOSITE_HARD_LIGHT = "hard_light"; const QString COMPOSITE_SOFT_LIGHT_IFS_ILLUSIONS = "soft_light_ifs_illusions"; const QString COMPOSITE_SOFT_LIGHT_PEGTOP_DELPHI = "soft_light_pegtop_delphi"; const QString COMPOSITE_SOFT_LIGHT_PHOTOSHOP = "soft_light"; const QString COMPOSITE_SOFT_LIGHT_SVG = "soft_light_svg"; const QString COMPOSITE_GAMMA_LIGHT = "gamma_light"; const QString COMPOSITE_GAMMA_ILLUMINATION = "gamma_illumination"; const QString COMPOSITE_VIVID_LIGHT = "vivid_light"; const QString COMPOSITE_FLAT_LIGHT = "flat_light"; const QString COMPOSITE_LINEAR_LIGHT = "linear light"; const QString COMPOSITE_PIN_LIGHT = "pin_light"; const QString COMPOSITE_PNORM_A = "pnorm_a"; const QString COMPOSITE_PNORM_B = "pnorm_b"; const QString COMPOSITE_SUPER_LIGHT = "super_light"; const QString COMPOSITE_TINT_IFS_ILLUSIONS = "tint_ifs_illusions"; const QString COMPOSITE_FOG_LIGHTEN_IFS_ILLUSIONS = "fog_lighten_ifs_illusions"; const QString COMPOSITE_EASY_DODGE = "easy dodge"; +const QString COMPOSITE_LUMINOSITY_SAI = "luminosity_sai"; + const QString COMPOSITE_HUE = "hue"; const QString COMPOSITE_COLOR = "color"; const QString COMPOSITE_SATURATION = "saturation"; const QString COMPOSITE_INC_SATURATION = "inc_saturation"; const QString COMPOSITE_DEC_SATURATION = "dec_saturation"; const QString COMPOSITE_LUMINIZE = "luminize"; const QString COMPOSITE_INC_LUMINOSITY = "inc_luminosity"; const QString COMPOSITE_DEC_LUMINOSITY = "dec_luminosity"; const QString COMPOSITE_HUE_HSV = "hue_hsv"; const QString COMPOSITE_COLOR_HSV = "color_hsv"; const QString COMPOSITE_SATURATION_HSV = "saturation_hsv"; const QString COMPOSITE_INC_SATURATION_HSV = "inc_saturation_hsv"; const QString COMPOSITE_DEC_SATURATION_HSV = "dec_saturation_hsv"; const QString COMPOSITE_VALUE = "value"; const QString COMPOSITE_INC_VALUE = "inc_value"; const QString COMPOSITE_DEC_VALUE = "dec_value"; const QString COMPOSITE_HUE_HSL = "hue_hsl"; const QString COMPOSITE_COLOR_HSL = "color_hsl"; const QString COMPOSITE_SATURATION_HSL = "saturation_hsl"; const QString COMPOSITE_INC_SATURATION_HSL = "inc_saturation_hsl"; const QString COMPOSITE_DEC_SATURATION_HSL = "dec_saturation_hsl"; const QString COMPOSITE_LIGHTNESS = "lightness"; const QString COMPOSITE_INC_LIGHTNESS = "inc_lightness"; const QString COMPOSITE_DEC_LIGHTNESS = "dec_lightness"; const QString COMPOSITE_HUE_HSI = "hue_hsi"; const QString COMPOSITE_COLOR_HSI = "color_hsi"; const QString COMPOSITE_SATURATION_HSI = "saturation_hsi"; const QString COMPOSITE_INC_SATURATION_HSI = "inc_saturation_hsi"; const QString COMPOSITE_DEC_SATURATION_HSI = "dec_saturation_hsi"; const QString COMPOSITE_INTENSITY = "intensity"; const QString COMPOSITE_INC_INTENSITY = "inc_intensity"; const QString COMPOSITE_DEC_INTENSITY = "dec_intensity"; const QString COMPOSITE_COPY = "copy"; const QString COMPOSITE_COPY_RED = "copy_red"; const QString COMPOSITE_COPY_GREEN = "copy_green"; const QString COMPOSITE_COPY_BLUE = "copy_blue"; const QString COMPOSITE_TANGENT_NORMALMAP = "tangent_normalmap"; const QString COMPOSITE_COLORIZE = "colorize"; const QString COMPOSITE_BUMPMAP = "bumpmap"; const QString COMPOSITE_COMBINE_NORMAL = "combine_normal"; const QString COMPOSITE_CLEAR = "clear"; const QString COMPOSITE_DISSOLVE = "dissolve"; const QString COMPOSITE_DISPLACE = "displace"; const QString COMPOSITE_NO = "nocomposition"; const QString COMPOSITE_PASS_THROUGH = "pass through"; // XXX: not implemented anywhere yet const QString COMPOSITE_DARKER_COLOR = "darker color"; const QString COMPOSITE_LIGHTER_COLOR = "lighter color"; const QString COMPOSITE_UNDEF = "undefined"; const QString COMPOSITE_REFLECT = "reflect"; const QString COMPOSITE_GLOW = "glow"; const QString COMPOSITE_FREEZE = "freeze"; const QString COMPOSITE_HEAT = "heat"; const QString COMPOSITE_GLEAT = "glow_heat"; const QString COMPOSITE_HELOW = "heat_glow"; const QString COMPOSITE_REEZE = "reflect_freeze"; const QString COMPOSITE_FRECT = "freeze_reflect"; const QString COMPOSITE_FHYRD = "heat_glow_freeze_reflect_hybrid"; class KRITAPIGMENT_EXPORT KoCompositeOpRegistry { typedef QMultiMap KoIDMap; typedef QList KoIDList; public: KoCompositeOpRegistry(); static const KoCompositeOpRegistry& instance(); KoID getDefaultCompositeOp() const; KoID getKoID(const QString& compositeOpID) const; KoIDMap getCompositeOps() const; KoIDList getCategories() const; KoIDList getCompositeOps(const KoColorSpace* colorSpace) const; KoIDList getCompositeOps(const KoID& category, const KoColorSpace* colorSpace=0) const; bool colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const; template KoIDList filterCompositeOps(TKoIdIterator begin, TKoIdIterator end, const KoColorSpace* colorSpace, bool removeInvaliOps=true) const { KoIDList list; for(; begin!=end; ++begin){ - if( colorSpaceHasCompositeOp(colorSpace, *begin) == removeInvaliOps) + if (colorSpaceHasCompositeOp(colorSpace, *begin) == removeInvaliOps) { list.push_back(*begin); + } } return list; } private: KoIDList m_categories; KoIDMap m_map; }; #endif // KOCOMPOSITEOPREGISTRY_H diff --git a/libs/pigment/compositeops/KoCompositeOpBase.h b/libs/pigment/compositeops/KoCompositeOpBase.h index a6378483a7..4e2c932454 100644 --- a/libs/pigment/compositeops/KoCompositeOpBase.h +++ b/libs/pigment/compositeops/KoCompositeOpBase.h @@ -1,134 +1,134 @@ /* * Copyright (c) 2011 Silvio Heinrich * * 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 KOCOMPOSITEOP_BASE_H_ #define KOCOMPOSITEOP_BASE_H_ #include #include "KoCompositeOpFunctions.h" /** * A template base class that can be used for most composite modes/ops * * @param _compositeOp this template parameter is a class that must be * derived fom KoCompositeOpBase and must define the static member function * template * inline static channels_type composeColorChannels( * const channels_type* src, * channels_type srcAlpha, * channels_type* dst, * channels_type dstAlpha, * channels_type maskAlpha, * channels_type opacity, * const QBitArray& channelFlags * ) * * where channels_type is _CSTraits::channels_type */ template class KoCompositeOpBase : public KoCompositeOp { typedef typename _CSTraits::channels_type channels_type; static const qint32 channels_nb = _CSTraits::channels_nb; static const qint32 alpha_pos = _CSTraits::alpha_pos; static const qint32 pixel_size = _CSTraits::pixelSize; public: KoCompositeOpBase(const KoColorSpace* cs, const QString& id, const QString& description, const QString& category) : KoCompositeOp(cs, id, description, category) { } using KoCompositeOp::composite; void composite(const KoCompositeOp::ParameterInfo& params) const override { const QBitArray& flags = params.channelFlags.isEmpty() ? QBitArray(channels_nb,true) : params.channelFlags; bool allChannelFlags = params.channelFlags.isEmpty() || params.channelFlags == QBitArray(channels_nb,true); bool alphaLocked = (alpha_pos != -1) && !flags.testBit(alpha_pos); bool useMask = params.maskRowStart != 0; if(useMask) { if(alphaLocked) { if(allChannelFlags) { genericComposite (params, flags); } else { genericComposite(params, flags); } } else { if(allChannelFlags) { genericComposite (params, flags); } else { genericComposite(params, flags); } } } else { if(alphaLocked) { if(allChannelFlags) { genericComposite (params, flags); } else { genericComposite(params, flags); } } else { if(allChannelFlags) { genericComposite (params, flags); } else { genericComposite(params, flags); } } } } private: template void genericComposite(const KoCompositeOp::ParameterInfo& params, const QBitArray& channelFlags) const { using namespace Arithmetic; qint32 srcInc = (params.srcRowStride == 0) ? 0 : channels_nb; channels_type opacity = scale(params.opacity); quint8* dstRowStart = params.dstRowStart; const quint8* srcRowStart = params.srcRowStart; const quint8* maskRowStart = params.maskRowStart; - for(qint32 r=0; r(srcRowStart); channels_type* dst = reinterpret_cast(dstRowStart); const quint8* mask = maskRowStart; - for(qint32 c=0; c() : src[alpha_pos]; channels_type dstAlpha = (alpha_pos == -1) ? unitValue() : dst[alpha_pos]; channels_type mskAlpha = useMask ? scale(*mask) : unitValue(); if (!allChannelFlags && dstAlpha == zeroValue()) { - memset(dst, 0, pixel_size); + memset(reinterpret_cast(dst), 0, pixel_size); } channels_type newDstAlpha = _compositeOp::template composeColorChannels( src, srcAlpha, dst, dstAlpha, mskAlpha, opacity, channelFlags ); if(alpha_pos != -1) dst[alpha_pos] = alphaLocked ? dstAlpha : newDstAlpha; src += srcInc; dst += channels_nb; if(useMask) ++mask; } srcRowStart += params.srcRowStride; dstRowStart += params.dstRowStride; maskRowStart += params.maskRowStride; } } }; #endif // KOCOMPOSITEOP_BASE_H_ diff --git a/libs/pigment/compositeops/KoCompositeOpFunctions.h b/libs/pigment/compositeops/KoCompositeOpFunctions.h index 8102482d5b..d51ad77299 100644 --- a/libs/pigment/compositeops/KoCompositeOpFunctions.h +++ b/libs/pigment/compositeops/KoCompositeOpFunctions.h @@ -1,936 +1,952 @@ /* * Copyright (c) 2011 Silvio Heinrich * * 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 KOCOMPOSITEOP_FUNCTIONS_H_ #define KOCOMPOSITEOP_FUNCTIONS_H_ #include template inline void cfReorientedNormalMapCombine(TReal srcR, TReal srcG, TReal srcB, TReal& dstR, TReal& dstG, TReal& dstB) { // see http://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill TReal tx = 2*srcR-1; TReal ty = 2*srcG-1; TReal tz = 2*srcB; TReal ux = -2*dstR+1; TReal uy = -2*dstG+1; TReal uz = 2*dstB-1; TReal k = (tx*ux+ty*uy+tz*uz)/tz; // dot(t,u)/t.z TReal rx = tx*k-ux; TReal ry = ty*k-uy; TReal rz = tz*k-uz; k = 1/sqrt(rx*rx+ry*ry+rz*rz); // normalize result rx *= k; ry *= k; rz *= k; dstR = rx*0.5+0.5; dstG = ry*0.5+0.5; dstB = rz*0.5+0.5; } template inline void cfColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setLightness(dr, dg, db, lum); } template inline void cfLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { setLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfIncreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfDecreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb) - TReal(1.0)); } template inline void cfSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(sr, sg, sb); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfIncreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(getSaturation(dr,dg,db), unitValue(), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfDecreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(zeroValue(), getSaturation(dr,dg,db), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfHue(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(dr, dg, db); TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, lum); } template inline void cfTangentNormalmap(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal half=halfValue(); dr = sr+(dr-half); dg = sg+(dg-half); db = sb+(db-unitValue()); } template inline void cfDarkerColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum inline void cfLighterColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum>lum2) { sr = dr; sg = dg; sb = db; } else { dr = sr; dg = sg; db = sb; } } template inline T cfColorBurn(T src, T dst) { using namespace Arithmetic; if(dst == unitValue()) return unitValue(); T invDst = inv(dst); if(src < invDst) return zeroValue(); return inv(clamp(div(invDst, src))); } template inline T cfLinearBurn(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(src) + dst - unitValue()); } template inline T cfColorDodge(T src, T dst) { using namespace Arithmetic; //Fixing Color Dodge to avoid ZX Colors on bright area. if(src == unitValue()) return unitValue(); T invSrc = inv(src); if(invSrc == zeroValue()) return unitValue(); return Arithmetic::clamp(div(dst, invSrc)); } template inline T cfAddition(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return Arithmetic::clamp(composite_type(src) + dst); } template inline T cfSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src); } template inline T cfInverseSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - inv(src)); } template inline T cfExclusion(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type x = mul(src, dst); return clamp(composite_type(dst) + src - (x + x)); } template inline T cfDivide(T src, T dst) { using namespace Arithmetic; //typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src == zeroValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); return clamp(div(dst, src)); } template inline T cfHardLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type src2 = composite_type(src) + src; if(src > halfValue()) { // screen(src*2.0 - 1.0, dst) src2 -= unitValue(); // src2 is guaranteed to be smaller than unitValue() now return Arithmetic::unionShapeOpacity(T(src2), dst); } // src2 is guaranteed to be smaller than unitValue() due to 'if' return Arithmetic::mul(T(src2), dst); } template inline T cfSoftLightSvg(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { qreal D = (fdst > 0.25f) ? sqrt(fdst) : ((16.0f*fdst - 12.0)*fdst + 4.0f)*fdst; return scale(fdst + (2.0f*fsrc - 1.0f) * (D - fdst)); } return scale(fdst - (1.0f - 2.0f * fsrc) * fdst * (1.0f - fdst)); } template inline T cfSoftLight(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { return scale(fdst + (2.0f * fsrc - 1.0f) * (sqrt(fdst) - fdst)); } return scale(fdst - (1.0f - 2.0f*fsrc) * fdst * (1.0f - fdst)); } template inline T cfVividLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src < halfValue()) { if(src == zeroValue()) return (dst == unitValue()) ? unitValue() : zeroValue(); // min(1,max(0,1-(1-dst) / (2*src))) composite_type src2 = composite_type(src) + src; composite_type dsti = inv(dst); return clamp(unitValue() - (dsti * unitValue() / src2)); } if(src == unitValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); // min(1,max(0, dst / (2*(1-src))) composite_type srci2 = inv(src); srci2 += srci2; return clamp(composite_type(dst) * unitValue() / srci2); } template inline T cfPinLight(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // TODO: verify that the formula is correct (the first max would be useless here) // max(0, max(2*src-1, min(dst, 2*src))) composite_type src2 = composite_type(src) + src; composite_type a = qMin(dst, src2); composite_type b = qMax(src2-Arithmetic::unitValue(), a); return T(b); } template inline T cfArcTangent(T src, T dst) { using namespace Arithmetic; if(dst == zeroValue()) return (src == zeroValue()) ? zeroValue() : unitValue(); return scale(2.0 * atan(scale(src) / scale(dst)) / Arithmetic::pi); } template inline T cfAllanon(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // (dst + src) / 2 [or (dst + src) * 0.5] return T((composite_type(src) + dst) * halfValue() / unitValue()); } template inline T cfLinearLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(1,max(0,(dst + 2*src)-1)) return clamp((composite_type(src) + src + dst) - unitValue()); } template inline T cfParallel(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(max(2 / (1/dst + 1/src), 0), 1) composite_type unit = unitValue(); composite_type s = (src != zeroValue()) ? div(unit, src) : unit; composite_type d = (dst != zeroValue()) ? div(unit, dst) : unit; if (src == zeroValue()) { return zeroValue(); } if (dst == zeroValue()) { return zeroValue(); } return clamp((unit+unit) * unit / (d+s)); } template inline T cfEquivalence(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // 1 - abs(dst - src) composite_type x = composite_type(dst) - src; return (x < Arithmetic::zeroValue()) ? T(-x) : T(x); } template inline T cfGrainMerge(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) + src - halfValue()); } template inline T cfGrainExtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src + halfValue()); } template inline T cfHardMix(T src, T dst) { return (dst > Arithmetic::halfValue()) ? cfColorDodge(src,dst) : cfColorBurn(src,dst); } template inline T cfHardMixPhotoshop(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; const composite_type sum = composite_type(src) + dst; return sum > unitValue() ? unitValue() : zeroValue(); } template inline T cfAdditiveSubtractive(T src, T dst) { using namespace Arithmetic; // min(1,max(0,abs(sqr(CB)-sqr(CT)))) qreal x = sqrt(scale(dst)) - sqrt(scale(src)); return scale((x < 0.0) ? -x : x); } template inline T cfGammaDark(T src, T dst) { using namespace Arithmetic; if(src == zeroValue()) return zeroValue(); // power(dst, 1/src) return scale(pow(scale(dst), 1.0/scale(src))); } template inline T cfGammaLight(T src, T dst) { using namespace Arithmetic; return scale(pow(scale(dst), scale(src))); } template inline T cfGammaIllumination(T src, T dst) { using namespace Arithmetic; return inv(cfGammaDark(inv(src),inv(dst))); } template inline T cfGeometricMean(T src, T dst) { using namespace Arithmetic; return scale(sqrt(scale(dst) * scale(src))); } template inline T cfOver(T src, T dst) { Q_UNUSED(dst); return src; } template inline T cfOverlay(T src, T dst) { return cfHardLight(dst, src); } template inline T cfMultiply(T src, T dst) { return Arithmetic::mul(src, dst); } template inline T cfHardOverlay(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0) { return scale(1.0);} if(fsrc > 0.5f) { return scale(cfDivide(inv(2.0 * fsrc - 1.0f), fdst)); } return scale(mul(2.0 * fsrc, fdst)); } template inline T cfDifference(T src, T dst) { return qMax(src,dst) - qMin(src,dst); } template inline T cfScreen(T src, T dst) { return Arithmetic::unionShapeOpacity(src, dst); } template inline T cfDarkenOnly(T src, T dst) { return qMin(src, dst); } template inline T cfLightenOnly(T src, T dst) { return qMax(src, dst); } template inline T cfGlow(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if (dst == unitValue()) { return unitValue(); } return clamp(div(mul(src, src), inv(dst))); } template inline T cfReflect(T src, T dst) { using namespace Arithmetic; return clamp(cfGlow(dst,src)); } template inline T cfHeat(T src, T dst) { using namespace Arithmetic; if(src == unitValue()) { return unitValue(); } if(dst == zeroValue()) { return zeroValue(); } return inv(clamp(div(mul(inv(src), inv(src)),dst))); } template inline T cfFreeze(T src, T dst) { using namespace Arithmetic; return (cfHeat(dst,src)); } template inline T cfHelow(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if (cfHardMixPhotoshop(src,dst) == unitValue()) { return cfHeat(src,dst); } if (src == zeroValue()) { return zeroValue(); } return (cfGlow(src,dst)); } template inline T cfFrect(T src, T dst) { using namespace Arithmetic; if (cfHardMixPhotoshop(src,dst) == unitValue()) { return cfFreeze(src,dst); } if (dst == zeroValue()) { return zeroValue(); } return (cfReflect(src,dst)); } template inline T cfGleat(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if(dst == unitValue()) { return unitValue(); } if(cfHardMixPhotoshop(src,dst) == unitValue()) { return cfGlow(src,dst); } return (cfHeat(src,dst)); } template inline T cfReeze(T src, T dst) { using namespace Arithmetic; return (cfGleat(dst,src)); } template inline T cfFhyrd(T src, T dst) { using namespace Arithmetic; return (cfAllanon(cfFrect(src,dst),cfHelow(src,dst))); } template inline T cfInterpolation(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(dst == zeroValue() && src == zeroValue()) { return zeroValue(); } return scale(.5f-.25f*cos(pi*(fsrc))-.25f*cos(pi*(fdst))); } template inline T cfInterpolationB(T src, T dst) { using namespace Arithmetic; return cfInterpolation(cfInterpolation(src,dst),cfInterpolation(src,dst)); } template inline T cfPenumbraB(T src, T dst) { using namespace Arithmetic; if (dst == unitValue()) { return unitValue(); } if (dst + src < unitValue()) { return (cfColorDodge(dst,src)/2); } if (src == zeroValue()) { return zeroValue(); } return inv(clamp(div(inv(dst),src)/2)); } template inline T cfPenumbraD(T src, T dst) { using namespace Arithmetic; if (dst == unitValue()) { return unitValue(); } return cfArcTangent(src,inv(dst)); } template inline T cfPenumbraC(T src, T dst) { using namespace Arithmetic; return cfPenumbraD(dst,src); } template inline T cfPenumbraA(T src, T dst) { using namespace Arithmetic; return (cfPenumbraB(dst,src)); } template inline T cfSoftLightIFSIllusions(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(pow(fdst,pow(2.0,(mul(2.0,.5f-fsrc))))); } template inline T cfSoftLightPegtopDelphi(T src, T dst) { using namespace Arithmetic; return clamp(cfAddition(mul(dst,cfScreen(src,dst)),mul(mul(src,dst),inv(dst)))); } template inline T cfNegation(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type unit = unitValue(); composite_type a = unit - src - dst; composite_type s = abs(a); composite_type d = unit - s; return T(d); } template inline T cfNor(T src, T dst) { using namespace Arithmetic; return and(src,dst); } template inline T cfNand(T src, T dst) { using namespace Arithmetic; return or(src,dst); } template inline T cfXor(T src, T dst) { using namespace Arithmetic; return xor(src,dst); } template inline T cfXnor(T src, T dst) { using namespace Arithmetic; return cfXor(src,inv(dst)); } template inline T cfAnd(T src, T dst) { using namespace Arithmetic; return cfNor(inv(src),inv(dst)); } template inline T cfOr(T src, T dst) { using namespace Arithmetic; return cfNand(inv(src),inv(dst)); } template inline T cfConverse(T src, T dst) { using namespace Arithmetic; return cfOr(inv(src),dst); } template inline T cfNotConverse(T src, T dst) { using namespace Arithmetic; return cfAnd(src,inv(dst)); } template inline T cfImplies(T src, T dst) { using namespace Arithmetic; return cfOr(src,inv(dst)); } template inline T cfNotImplies(T src, T dst) { using namespace Arithmetic; return cfAnd(inv(src),dst); } template inline T cfPNormA(T src, T dst) { using namespace Arithmetic; //This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875... return clamp(pow(pow((float)dst, 2.3333333333333333) + pow((float)src, 2.3333333333333333), 0.428571428571434)); } template inline T cfPNormB(T src, T dst) { using namespace Arithmetic; //This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875... return clamp(pow(pow(dst,4)+pow(src,4),0.25)); } template inline T cfSuperLight(T src, T dst) { using namespace Arithmetic; //4.0 can be adjusted to taste. 4.0 is picked for being the best in terms of contrast and details. See imblend.m file. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(pow(pow(inv(fdst),2.875)+pow(inv(2.0*fsrc),2.875),1.0/2.875))); } return scale(pow(pow(fdst,2.875)+pow(2.0*fsrc-1.0,2.875),1.0/2.875)); } template inline T cfTintIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Light Blending mode found in IFS Illusions. Picked this name because it results into a very strong tint, and has better naming convention. qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(fsrc*inv(fdst)+sqrt(fdst)); } template inline T cfShadeIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Shadow Blending mode found in IFS Illusions. Picked this name because it is the opposite of Tint (IFS Illusion Blending mode). qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(inv((inv(fdst)*fsrc)+sqrt(inv(fsrc)))); } template inline T cfFogLightenIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Bright Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradientt. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(inv(fsrc)*fsrc)-inv(fdst)*inv(fsrc)); } return scale(fsrc-inv(fdst)*inv(fsrc)+pow(inv(fsrc),2)); } template inline T cfFogDarkenIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Dark Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(fsrc)*fsrc+fsrc*fdst); } return scale(fsrc*fdst+fsrc-pow(fsrc,2)); } template inline T cfModulo(T src, T dst) { using namespace Arithmetic; return mod(dst,src); } template inline T cfModuloShift(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0 && fdst == 0.0) { return scale(0.0); } return scale(mod((fdst+fsrc),1.0000000000)); } template inline T cfModuloShiftContinuous(T src, T dst) { using namespace Arithmetic; //This blending mode do not behave like difference/equilavent with destination layer inverted if you use group layer on addition while the content of group layer contains several addition-mode layers, it works as expected on float images. So, no need to change this. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0 && fdst == 0.0) { return scale(1.0); } return scale((int(ceil(fdst+fsrc)) % 2 != 0) || (fdst == zeroValue()) ? cfModuloShift(fsrc,fdst) : inv(cfModuloShift(fsrc,fdst))); } template inline T cfDivisiveModulo(T src, T dst) { using namespace Arithmetic; //I have to use 1.00000 as unitValue failed to work for those area. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == zeroValue()) { return scale(mod(((1.0000000000/epsilon()) * fdst),1.0000000000)); } return scale(mod(((1.0000000000/fsrc) * fdst),1.0000000000)); } template inline T cfDivisiveModuloContinuous(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fdst == zeroValue()) { return zeroValue(); } if (fsrc == zeroValue()) { return cfDivisiveModulo(fsrc,fdst); } return scale( int(ceil(fdst/fsrc)) % 2 != 0 ? cfDivisiveModulo(fsrc,fdst) : inv(cfDivisiveModulo(fsrc,fdst))); } template inline T cfModuloContinuous(T src, T dst) { using namespace Arithmetic; return cfMultiply(cfDivisiveModuloContinuous(src,dst),src); } template inline T cfEasyDodge(T src, T dst) { using namespace Arithmetic; // The 13 divided by 15 can be adjusted to taste. See imgblend.m qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0) { return scale(1.0);} return scale(pow(fdst,mul(inv(fsrc != 1.0 ? fsrc : .999999999999),1.039999999))); } template inline T cfEasyBurn(T src, T dst) { using namespace Arithmetic; // The 13 divided by 15 can be adjusted to taste. See imgblend.m qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(inv(pow(inv(fsrc != 1.0 ? fsrc : .999999999999),mul(fdst,1.039999999)))); } template inline T cfFlatLight(T src, T dst) { using namespace Arithmetic; if (src == zeroValue()) { return zeroValue(); } return clamp(cfHardMixPhotoshop(inv(src),dst)==unitValue() ? cfPenumbraB(src,dst) : cfPenumbraA(src,dst)); } + + +template +inline void cfAdditionSAI(TReal src, TReal sa, TReal& dst, TReal& da) +{ + using namespace Arithmetic; + typedef typename KoColorSpaceMathsTraits::compositetype composite_type; + + Q_UNUSED(da); + composite_type newsrc; + newsrc = mul(src, sa); + dst = clamp(newsrc + dst); +} + + + #endif // KOCOMPOSITEOP_FUNCTIONS_H_ diff --git a/libs/pigment/compositeops/KoCompositeOpGeneric.h b/libs/pigment/compositeops/KoCompositeOpGeneric.h index ac7a0c75b4..a4d9baeed6 100644 --- a/libs/pigment/compositeops/KoCompositeOpGeneric.h +++ b/libs/pigment/compositeops/KoCompositeOpGeneric.h @@ -1,169 +1,242 @@ /* * Copyright (c) 2011 Silvio Heinrich * * 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 _KOCOMPOSITEOP_GENERIC_H_ #define _KOCOMPOSITEOP_GENERIC_H_ #include "KoCompositeOpFunctions.h" #include "KoCompositeOpBase.h" /** * Generic CompositeOp for separable channel compositing functions * * A template to generate a KoCompositeOp class by just specifying a * blending/compositing function. This template works with compositing functions * for separable channels (means each channel of a pixel can be processed separately) */ template< class Traits, typename Traits::channels_type compositeFunc(typename Traits::channels_type, typename Traits::channels_type) > class KoCompositeOpGenericSC: public KoCompositeOpBase< Traits, KoCompositeOpGenericSC > { typedef KoCompositeOpBase< Traits, KoCompositeOpGenericSC > base_class; typedef typename Traits::channels_type channels_type; static const qint32 channels_nb = Traits::channels_nb; static const qint32 alpha_pos = Traits::alpha_pos; public: KoCompositeOpGenericSC(const KoColorSpace* cs, const QString& id, const QString& description, const QString& category) : base_class(cs, id, description, category) { } public: template inline static channels_type composeColorChannels(const channels_type* src, channels_type srcAlpha, channels_type* dst, channels_type dstAlpha, channels_type maskAlpha, channels_type opacity, const QBitArray& channelFlags) { using namespace Arithmetic; srcAlpha = mul(srcAlpha, maskAlpha, opacity); if(alphaLocked) { if(dstAlpha != zeroValue()) { for(qint32 i=0; i ()) { for(qint32 i=0; i class KoCompositeOpGenericHSL: public KoCompositeOpBase< Traits, KoCompositeOpGenericHSL > { typedef KoCompositeOpBase< Traits, KoCompositeOpGenericHSL > base_class; typedef typename Traits::channels_type channels_type; static const qint32 red_pos = Traits::red_pos; static const qint32 green_pos = Traits::green_pos; static const qint32 blue_pos = Traits::blue_pos; public: KoCompositeOpGenericHSL(const KoColorSpace* cs, const QString& id, const QString& description, const QString& category) : base_class(cs, id, description, category) { } public: template inline static channels_type composeColorChannels(const channels_type* src, channels_type srcAlpha, channels_type* dst, channels_type dstAlpha, channels_type maskAlpha, channels_type opacity, const QBitArray& channelFlags) { using namespace Arithmetic; - + srcAlpha = mul(srcAlpha, maskAlpha, opacity); - + if(alphaLocked) { if(dstAlpha != zeroValue()) { float srcR = scale(src[red_pos]); float srcG = scale(src[green_pos]); float srcB = scale(src[blue_pos]); - + float dstR = scale(dst[red_pos]); float dstG = scale(dst[green_pos]); float dstB = scale(dst[blue_pos]); - + compositeFunc(srcR, srcG, srcB, dstR, dstG, dstB); - + if(allChannelFlags || channelFlags.testBit(red_pos)) dst[red_pos] = lerp(dst[red_pos], scale(dstR), srcAlpha); - + if(allChannelFlags || channelFlags.testBit(green_pos)) dst[green_pos] = lerp(dst[green_pos], scale(dstG), srcAlpha); - + if(allChannelFlags || channelFlags.testBit(blue_pos)) dst[blue_pos] = lerp(dst[blue_pos], scale(dstB), srcAlpha); } - + return dstAlpha; } else { channels_type newDstAlpha = unionShapeOpacity(srcAlpha, dstAlpha); - + if(newDstAlpha != zeroValue()) { float srcR = scale(src[red_pos]); float srcG = scale(src[green_pos]); float srcB = scale(src[blue_pos]); - + float dstR = scale(dst[red_pos]); float dstG = scale(dst[green_pos]); float dstB = scale(dst[blue_pos]); - + compositeFunc(srcR, srcG, srcB, dstR, dstG, dstB); - + if(allChannelFlags || channelFlags.testBit(red_pos)) dst[red_pos] = div(blend(src[red_pos], srcAlpha, dst[red_pos], dstAlpha, scale(dstR)), newDstAlpha); - + if(allChannelFlags || channelFlags.testBit(green_pos)) dst[green_pos] = div(blend(src[green_pos], srcAlpha, dst[green_pos], dstAlpha, scale(dstG)), newDstAlpha); - + if(allChannelFlags || channelFlags.testBit(blue_pos)) dst[blue_pos] = div(blend(src[blue_pos], srcAlpha, dst[blue_pos], dstAlpha, scale(dstB)), newDstAlpha); } - + return newDstAlpha; } } }; + + +/** + * Generic CompositeOp for separable channel + alpha compositing functions + * + * A template to generate a KoCompositeOp class by just specifying a + * blending/compositing function. This template works with compositing functions + * for separable channels (means each channel of a pixel can be processed separately) + * with taking apha into consideration. + * Note that because of special treating of alpha, any composite op function + * needs to make alpha blending itself - the value of color that is written onto the projection + * is the same that the composite function gives (compare with KoCompositeOpGenericHSL and KoCompositeOpGenericSC). + */ +template +class KoCompositeOpGenericSCAlpha: public KoCompositeOpBase< Traits, KoCompositeOpGenericSCAlpha > +{ + typedef KoCompositeOpBase< Traits, KoCompositeOpGenericSCAlpha > base_class; + typedef typename Traits::channels_type channels_type; + + static const qint32 channels_nb = Traits::channels_nb; + static const qint32 alpha_pos = Traits::alpha_pos; + +public: + KoCompositeOpGenericSCAlpha(const KoColorSpace* cs, const QString& id, const QString& description, const QString& category) + : base_class(cs, id, description, category) { } + +public: + template + inline static channels_type composeColorChannels(const channels_type* src, channels_type srcAlpha, + channels_type* dst, channels_type dstAlpha, channels_type maskAlpha, + channels_type opacity, const QBitArray& channelFlags) + { + using namespace Arithmetic; + + srcAlpha = mul(srcAlpha, maskAlpha, opacity); + + if(alphaLocked) { + channels_type oldAlpha = dstAlpha; + if(dstAlpha != zeroValue()) { + for(qint32 i=0; i (dst[i]); + float dstAlphaFloat = scale(oldAlpha); + compositeFunc(scale(src[i]), scale(srcAlpha), dstValueFloat, dstAlphaFloat); + dst[i] = scale(dstValueFloat); + } + } + } + + return dstAlpha; + } + else { + channels_type oldAlpha = dstAlpha; + channels_type newDstAlpha = unionShapeOpacity(srcAlpha, dstAlpha); + + if(newDstAlpha != zeroValue()) { + for(qint32 i=0; i (dst[i]); + float dstAlphaFloat = scale(oldAlpha); + compositeFunc(scale(src[i]), scale(srcAlpha), dstFloat, dstAlphaFloat); + dst[i] = scale(dstFloat); + } + } + } + + return newDstAlpha; + } + } +}; + + + #endif // _KOCOMPOSITEOP_GENERIC_H_ diff --git a/libs/pigment/compositeops/KoCompositeOps.h b/libs/pigment/compositeops/KoCompositeOps.h index 33b8d2b0e6..b29c4d287e 100644 --- a/libs/pigment/compositeops/KoCompositeOps.h +++ b/libs/pigment/compositeops/KoCompositeOps.h @@ -1,313 +1,364 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU 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 _KOCOMPOSITEOPS_H_ #define _KOCOMPOSITEOPS_H_ #include #include #include #include #include "compositeops/KoCompositeOpGeneric.h" #include "compositeops/KoCompositeOpOver.h" #include "compositeops/KoCompositeOpCopyChannel.h" #include "compositeops/KoCompositeOpAlphaDarken.h" #include "compositeops/KoCompositeOpErase.h" #include "compositeops/KoCompositeOpCopy2.h" #include "compositeops/KoCompositeOpDissolve.h" #include "compositeops/KoCompositeOpBehind.h" #include "compositeops/KoCompositeOpDestinationIn.h" #include "compositeops/KoCompositeOpDestinationAtop.h" #include "compositeops/KoCompositeOpGreater.h" #include "compositeops/KoAlphaDarkenParamsWrapper.h" #include "KoOptimizedCompositeOpFactory.h" namespace _Private { template struct AddGeneralOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { if (useCreamyAlphaDarken()) { return new KoCompositeOpAlphaDarken(cs); } else { return new KoCompositeOpAlphaDarken(cs); } } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return new KoCompositeOpOver(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return useCreamyAlphaDarken() ? KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs) : KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return useCreamyAlphaDarken() ? KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs) : KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { - return useCreamyAlphaDarken() ? - KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs) : - KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard128(cs); + + // TODO: optimized code is disabled for 4.2 release, + // becasue it causes bug https://bugs.kde.org/show_bug.cgi?id=404133 + if (useCreamyAlphaDarken()) { + return new KoCompositeOpAlphaDarken(cs); + } else { + return new KoCompositeOpAlphaDarken(cs); + } + + // TODO: please restore this optimized version when the bug is fixed + // return useCreamyAlphaDarken() ? + // KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs) : + // KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard128(cs); + } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp128(cs); } }; template struct AddGeneralOps { typedef typename Traits::channels_type Arg; typedef Arg (*CompositeFunc)(Arg, Arg); static const qint32 alpha_pos = Traits::alpha_pos; template static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) { cs->addCompositeOp(new KoCompositeOpGenericSC(cs, id, description, category)); } static void add(KoColorSpace* cs) { cs->addCompositeOp(OptimizedOpsSelector::createOverOp(cs)); cs->addCompositeOp(OptimizedOpsSelector::createAlphaDarkenOp(cs)); cs->addCompositeOp(new KoCompositeOpCopy2(cs)); cs->addCompositeOp(new KoCompositeOpErase(cs)); cs->addCompositeOp(new KoCompositeOpBehind(cs)); cs->addCompositeOp(new KoCompositeOpDestinationIn(cs)); cs->addCompositeOp(new KoCompositeOpDestinationAtop(cs)); cs->addCompositeOp(new KoCompositeOpGreater(cs)); add<&cfOverlay >(cs, COMPOSITE_OVERLAY , i18n("Overlay") , KoCompositeOp::categoryMix()); add<&cfGrainMerge >(cs, COMPOSITE_GRAIN_MERGE , i18n("Grain Merge") , KoCompositeOp::categoryMix()); add<&cfGrainExtract >(cs, COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract") , KoCompositeOp::categoryMix()); add<&cfHardMix >(cs, COMPOSITE_HARD_MIX , i18n("Hard Mix") , KoCompositeOp::categoryMix()); add<&cfHardMixPhotoshop>(cs, COMPOSITE_HARD_MIX_PHOTOSHOP, i18n("Hard Mix (Photoshop)") , KoCompositeOp::categoryMix()); add<&cfGeometricMean >(cs, COMPOSITE_GEOMETRIC_MEAN, i18n("Geometric Mean"), KoCompositeOp::categoryMix()); add<&cfParallel >(cs, COMPOSITE_PARALLEL , i18n("Parallel") , KoCompositeOp::categoryMix()); add<&cfAllanon >(cs, COMPOSITE_ALLANON , i18n("Allanon") , KoCompositeOp::categoryMix()); add<&cfHardOverlay >(cs, COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay") , KoCompositeOp::categoryMix()); add<&cfInterpolation >(cs, COMPOSITE_INTERPOLATION , i18n("Interpolation") , KoCompositeOp::categoryMix()); add<&cfInterpolationB >(cs, COMPOSITE_INTERPOLATIONB , i18n("Interpolation - 2X") , KoCompositeOp::categoryMix()); add<&cfPenumbraA >(cs, COMPOSITE_PENUMBRAA , i18n("Penumbra A") , KoCompositeOp::categoryMix()); add<&cfPenumbraB >(cs, COMPOSITE_PENUMBRAB , i18n("Penumbra B") , KoCompositeOp::categoryMix()); add<&cfPenumbraC >(cs, COMPOSITE_PENUMBRAC , i18n("Penumbra C") , KoCompositeOp::categoryMix()); add<&cfPenumbraD >(cs, COMPOSITE_PENUMBRAD , i18n("Penumbra D") , KoCompositeOp::categoryMix()); add<&cfScreen >(cs, COMPOSITE_SCREEN , i18n("Screen") , KoCompositeOp::categoryLight()); add<&cfColorDodge >(cs, COMPOSITE_DODGE , i18n("Color Dodge") , KoCompositeOp::categoryLight()); add<&cfAddition >(cs, COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"), KoCompositeOp::categoryLight()); add<&cfLightenOnly >(cs, COMPOSITE_LIGHTEN , i18n("Lighten") , KoCompositeOp::categoryLight()); add<&cfHardLight >(cs, COMPOSITE_HARD_LIGHT , i18n("Hard Light") , KoCompositeOp::categoryLight()); add<&cfSoftLightIFSIllusions>(cs, COMPOSITE_SOFT_LIGHT_IFS_ILLUSIONS, i18n("Soft Light (IFS Illusions)") , KoCompositeOp::categoryLight()); add<&cfSoftLightPegtopDelphi>(cs, COMPOSITE_SOFT_LIGHT_PEGTOP_DELPHI, i18n("Soft Light (Pegtop-Delphi)") , KoCompositeOp::categoryLight()); add<&cfSoftLightSvg >(cs, COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)") , KoCompositeOp::categoryLight()); add<&cfSoftLight >(cs, COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)") , KoCompositeOp::categoryLight()); add<&cfGammaLight >(cs, COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light") , KoCompositeOp::categoryLight()); add<&cfGammaIllumination >(cs, COMPOSITE_GAMMA_ILLUMINATION , i18n("Gamma Illumination") , KoCompositeOp::categoryLight()); add<&cfVividLight >(cs, COMPOSITE_VIVID_LIGHT , i18n("Vivid Light") , KoCompositeOp::categoryLight()); add<&cfFlatLight >(cs, COMPOSITE_FLAT_LIGHT , i18n("Flat Light") , KoCompositeOp::categoryLight()); add<&cfPinLight >(cs, COMPOSITE_PIN_LIGHT , i18n("Pin Light") , KoCompositeOp::categoryLight()); add<&cfLinearLight >(cs, COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"), KoCompositeOp::categoryLight()); add<&cfPNormA >(cs, COMPOSITE_PNORM_A , i18n("P-Norm A") , KoCompositeOp::categoryLight()); add<&cfPNormB >(cs, COMPOSITE_PNORM_B , i18n("P-Norm B") , KoCompositeOp::categoryLight()); add<&cfSuperLight >(cs, COMPOSITE_SUPER_LIGHT , i18n("Super Light") , KoCompositeOp::categoryLight()); add<&cfTintIFSIllusions >(cs, COMPOSITE_TINT_IFS_ILLUSIONS , i18n("Tint (IFS Illusions)") , KoCompositeOp::categoryLight()); add<&cfFogLightenIFSIllusions >(cs, COMPOSITE_FOG_LIGHTEN_IFS_ILLUSIONS , i18n("Fog Lighten (IFS Illusions)") , KoCompositeOp::categoryLight()); add<&cfEasyDodge >(cs, COMPOSITE_EASY_DODGE , i18n("Easy Dodge") , KoCompositeOp::categoryLight()); add<&cfColorBurn >(cs, COMPOSITE_BURN , i18n("Color Burn") , KoCompositeOp::categoryDark()); add<&cfLinearBurn >(cs, COMPOSITE_LINEAR_BURN , i18n("Linear Burn"), KoCompositeOp::categoryDark()); add<&cfDarkenOnly >(cs, COMPOSITE_DARKEN , i18n("Darken") , KoCompositeOp::categoryDark()); add<&cfGammaDark >(cs, COMPOSITE_GAMMA_DARK , i18n("Gamma Dark") , KoCompositeOp::categoryDark()); add<&cfShadeIFSIllusions >(cs, COMPOSITE_SHADE_IFS_ILLUSIONS , i18n("Shade (IFS_Illusions)") , KoCompositeOp::categoryDark()); add<&cfFogDarkenIFSIllusions >(cs, COMPOSITE_FOG_DARKEN_IFS_ILLUSIONS , i18n("Fog Darken (IFS Illusions)") , KoCompositeOp::categoryDark()); add<&cfEasyBurn >(cs, COMPOSITE_EASY_BURN , i18n("Easy Burn") , KoCompositeOp::categoryDark()); add<&cfAddition >(cs, COMPOSITE_ADD , i18n("Addition") , KoCompositeOp::categoryArithmetic()); add<&cfSubtract >(cs, COMPOSITE_SUBTRACT , i18n("Subtract") , KoCompositeOp::categoryArithmetic()); add<&cfInverseSubtract >(cs, COMPOSITE_INVERSE_SUBTRACT, i18n("Inversed-Subtract"), KoCompositeOp::categoryArithmetic()); add<&cfMultiply >(cs, COMPOSITE_MULT , i18n("Multiply") , KoCompositeOp::categoryArithmetic()); add<&cfDivide >(cs, COMPOSITE_DIVIDE , i18n("Divide") , KoCompositeOp::categoryArithmetic()); add<&cfModulo >(cs, COMPOSITE_MOD , i18n("Modulo") , KoCompositeOp::categoryModulo()); add<&cfModuloContinuous >(cs, COMPOSITE_MOD_CON , i18n("Modulo - Continuous") , KoCompositeOp::categoryModulo()); add<&cfDivisiveModulo >(cs, COMPOSITE_DIVISIVE_MOD , i18n("Divisive Modulo") , KoCompositeOp::categoryModulo()); add<&cfDivisiveModuloContinuous >(cs, COMPOSITE_DIVISIVE_MOD_CON , i18n("Divisive Modulo - Continuous") , KoCompositeOp::categoryModulo()); add<&cfModuloShift >(cs, COMPOSITE_MODULO_SHIFT , i18n("Modulo Shift") , KoCompositeOp::categoryModulo()); add<&cfModuloShiftContinuous >(cs, COMPOSITE_MODULO_SHIFT_CON , i18n("Modulo Shift - Continuous") , KoCompositeOp::categoryModulo()); add<&cfArcTangent >(cs, COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent") , KoCompositeOp::categoryNegative()); add<&cfDifference >(cs, COMPOSITE_DIFF , i18n("Difference") , KoCompositeOp::categoryNegative()); add<&cfExclusion >(cs, COMPOSITE_EXCLUSION , i18n("Exclusion") , KoCompositeOp::categoryNegative()); add<&cfEquivalence >(cs, COMPOSITE_EQUIVALENCE , i18n("Equivalence") , KoCompositeOp::categoryNegative()); add<&cfAdditiveSubtractive >(cs, COMPOSITE_ADDITIVE_SUBTRACTIVE , i18n("Additive-Subtractive") , KoCompositeOp::categoryNegative()); add<&cfNegation >(cs, COMPOSITE_NEGATION , i18n("Negation") , KoCompositeOp::categoryNegative()); add<&cfXor >(cs, COMPOSITE_XOR , i18n("XOR") , KoCompositeOp::categoryBinary()); add<&cfOr >(cs, COMPOSITE_OR , i18n("OR") , KoCompositeOp::categoryBinary()); add<&cfAnd >(cs, COMPOSITE_AND , i18n("AND") , KoCompositeOp::categoryBinary()); add<&cfNand >(cs, COMPOSITE_NAND , i18n("NAND") , KoCompositeOp::categoryBinary()); add<&cfNor >(cs, COMPOSITE_NOR , i18n("NOR") , KoCompositeOp::categoryBinary()); add<&cfXnor >(cs, COMPOSITE_XNOR , i18n("XNOR") , KoCompositeOp::categoryBinary()); add<&cfImplies >(cs, COMPOSITE_IMPLICATION , i18n("IMPLICATION") , KoCompositeOp::categoryBinary()); add<&cfNotImplies >(cs, COMPOSITE_NOT_IMPLICATION , i18n("NOT IMPLICATION") , KoCompositeOp::categoryBinary()); add<&cfConverse >(cs, COMPOSITE_CONVERSE , i18n("CONVERSE") , KoCompositeOp::categoryBinary()); add<&cfNotConverse >(cs, COMPOSITE_NOT_CONVERSE , i18n("NOT CONVERSE") , KoCompositeOp::categoryBinary()); add<&cfReflect >(cs, COMPOSITE_REFLECT , i18n("Reflect") , KoCompositeOp::categoryQuadratic()); add<&cfGlow >(cs, COMPOSITE_GLOW , i18n("Glow") , KoCompositeOp::categoryQuadratic()); add<&cfFreeze >(cs, COMPOSITE_FREEZE , i18n("Freeze") , KoCompositeOp::categoryQuadratic()); add<&cfHeat >(cs, COMPOSITE_HEAT , i18n("Heat") , KoCompositeOp::categoryQuadratic()); add<&cfGleat >(cs, COMPOSITE_GLEAT , i18n("Glow-Heat") , KoCompositeOp::categoryQuadratic()); add<&cfHelow >(cs, COMPOSITE_HELOW , i18n("Heat-Glow") , KoCompositeOp::categoryQuadratic()); add<&cfReeze >(cs, COMPOSITE_REEZE , i18n("Reflect-Freeze") , KoCompositeOp::categoryQuadratic()); add<&cfFrect >(cs, COMPOSITE_FRECT , i18n("Freeze-Reflect") , KoCompositeOp::categoryQuadratic()); add<&cfFhyrd >(cs, COMPOSITE_FHYRD , i18n("Heat-Glow & Freeze-Reflect Hybrid") , KoCompositeOp::categoryQuadratic()); cs->addCompositeOp(new KoCompositeOpDissolve(cs, KoCompositeOp::categoryMisc())); } }; template struct AddRGBOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct AddRGBOps { typedef float Arg; static const qint32 red_pos = Traits::red_pos; static const qint32 green_pos = Traits::green_pos; static const qint32 blue_pos = Traits::blue_pos; template static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) { cs->addCompositeOp(new KoCompositeOpGenericHSL(cs, id, description, category)); } static void add(KoColorSpace* cs) { cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_RED , i18n("Copy Red") , KoCompositeOp::categoryMisc())); cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_GREEN, i18n("Copy Green"), KoCompositeOp::categoryMisc())); cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_BLUE , i18n("Copy Blue") , KoCompositeOp::categoryMisc())); add<&cfTangentNormalmap >(cs, COMPOSITE_TANGENT_NORMALMAP , i18n("Tangent Normalmap") , KoCompositeOp::categoryMisc()); add<&cfReorientedNormalMapCombine >(cs, COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Maps"), KoCompositeOp::categoryMisc()); add<&cfColor >(cs, COMPOSITE_COLOR , i18n("Color") , KoCompositeOp::categoryHSY()); add<&cfHue >(cs, COMPOSITE_HUE , i18n("Hue") , KoCompositeOp::categoryHSY()); add<&cfSaturation >(cs, COMPOSITE_SATURATION , i18n("Saturation") , KoCompositeOp::categoryHSY()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION, i18n("Increase Saturation"), KoCompositeOp::categoryHSY()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"), KoCompositeOp::categoryHSY()); add<&cfLightness >(cs, COMPOSITE_LUMINIZE , i18n("Luminosity") , KoCompositeOp::categoryHSY()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"), KoCompositeOp::categoryHSY()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"), KoCompositeOp::categoryHSY()); add<&cfDarkerColor >(cs, COMPOSITE_DARKER_COLOR, i18n("Darker Color"), KoCompositeOp::categoryDark());//darker color as PSD does it// add<&cfLighterColor >(cs, COMPOSITE_LIGHTER_COLOR, i18n("Lighter Color"), KoCompositeOp::categoryLight());//lighter color as PSD does it// add<&cfColor >(cs, COMPOSITE_COLOR_HSI , i18n("Color HSI") , KoCompositeOp::categoryHSI()); add<&cfHue >(cs, COMPOSITE_HUE_HSI , i18n("Hue HSI") , KoCompositeOp::categoryHSI()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSI , i18n("Saturation HSI") , KoCompositeOp::categoryHSI()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"), KoCompositeOp::categoryHSI()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"), KoCompositeOp::categoryHSI()); add<&cfLightness >(cs, COMPOSITE_INTENSITY , i18n("Intensity") , KoCompositeOp::categoryHSI()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_INTENSITY , i18n("Increase Intensity") , KoCompositeOp::categoryHSI()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity") , KoCompositeOp::categoryHSI()); add<&cfColor >(cs, COMPOSITE_COLOR_HSL , i18n("Color HSL") , KoCompositeOp::categoryHSL()); add<&cfHue >(cs, COMPOSITE_HUE_HSL , i18n("Hue HSL") , KoCompositeOp::categoryHSL()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSL , i18n("Saturation HSL") , KoCompositeOp::categoryHSL()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"), KoCompositeOp::categoryHSL()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"), KoCompositeOp::categoryHSL()); add<&cfLightness >(cs, COMPOSITE_LIGHTNESS , i18n("Lightness") , KoCompositeOp::categoryHSL()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness") , KoCompositeOp::categoryHSL()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness") , KoCompositeOp::categoryHSL()); add<&cfColor >(cs, COMPOSITE_COLOR_HSV , i18n("Color HSV") , KoCompositeOp::categoryHSV()); add<&cfHue >(cs, COMPOSITE_HUE_HSV , i18n("Hue HSV") , KoCompositeOp::categoryHSV()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSV , i18n("Saturation HSV") , KoCompositeOp::categoryHSV()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"), KoCompositeOp::categoryHSV()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"), KoCompositeOp::categoryHSV()); add<&cfLightness >(cs, COMPOSITE_VALUE , i18nc("HSV Value","Value") , KoCompositeOp::categoryHSV()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_VALUE , i18n("Increase Value") , KoCompositeOp::categoryHSV()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_VALUE , i18n("Decrease Value") , KoCompositeOp::categoryHSV()); } }; + + + +template +struct AddGeneralAlphaOps +{ + static void add(KoColorSpace* cs) { Q_UNUSED(cs); } +}; + +template +struct AddGeneralAlphaOps +{ + typedef float Arg; + static const qint32 alpha_pos = Traits::alpha_pos; + template + + + static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) + { + cs->addCompositeOp(new KoCompositeOpGenericSCAlpha(cs, id, description, category)); + } + + static void add(KoColorSpace* cs) + { + add<&cfAdditionSAI >(cs, COMPOSITE_LUMINOSITY_SAI , i18n("Luminosity/Shine (SAI)") , KoCompositeOp::categoryHSV()); + } + + +}; + + + + + } + + + + /** * This function add to the colorspace all the composite ops defined by * the pigment library. */ template void addStandardCompositeOps(KoColorSpace* cs) { typedef typename _Traits_::channels_type channels_type; static const bool useGeneralOps = true; static const bool useRGBOps = (boost::is_base_of, _Traits_>::value || boost::is_base_of, _Traits_>::value); - _Private::AddGeneralOps<_Traits_, useGeneralOps>::add(cs); - _Private::AddRGBOps <_Traits_, useRGBOps >::add(cs); + _Private::AddGeneralOps <_Traits_, useGeneralOps>::add(cs); + _Private::AddRGBOps <_Traits_, useRGBOps >::add(cs); + _Private::AddGeneralAlphaOps <_Traits_, useGeneralOps>::add(cs); + } template KoCompositeOp* createAlphaDarkenCompositeOp(const KoColorSpace *cs) { return _Private::OptimizedOpsSelector<_Traits_>::createAlphaDarkenOp(cs); } #endif diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 70f4945b25..aa80c8dc3e 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1652 +1,1657 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include "KisSwatch.h" #include "KoColorSet.h" #include "KoColorSet_p.h" namespace { /** * readAllLinesSafe() reads all the lines in the byte array * using the automated UTF8 and CR/LF transformations. That * might be necessary for opening GPL palettes created on Linux * in Windows environment. */ QStringList readAllLinesSafe(QByteArray *data) { QStringList lines; QBuffer buffer(data); buffer.open(QBuffer::ReadOnly); QTextStream stream(&buffer); QString line; while (stream.readLineInto(&line)) { lines << line; } return lines; } } const QString KoColorSet::GLOBAL_GROUP_NAME = QString(); const QString KoColorSet::KPL_VERSION_ATTR = "version"; const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows"; const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns"; const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name"; const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment"; const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename"; const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly"; const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId"; const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId"; const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row"; const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column"; const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id"; const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot"; const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth"; const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile"; const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position"; const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry"; const QString KoColorSet::KPL_GROUP_TAG = "Group"; const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet"; const int MAXIMUM_ALLOWED_COLUMNS = 4096; KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private(this)) { if (!filename.isEmpty()) { QFileInfo f(filename); setIsEditable(f.isWritable()); } } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(rhs) , d(new Private(this)) { d->paletteType = rhs.d->paletteType; d->data = rhs.d->data; d->comment = rhs.d->comment; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; d->isGlobal = rhs.d->isGlobal; d->isEditable = rhs.d->isEditable; } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); if (!QFileInfo(filename()).isWritable()) { setIsEditable(false); } return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return d->init(); } bool KoColorSet::save() { if (d->isGlobal) { // save to resource dir QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } else { return true; // palette is not global, but still indicate that it's saved } } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = d->saveGpl(dev); break; default: res = d->saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } QByteArray KoColorSet::toByteArray() const { QBuffer s; s.open(QIODevice::WriteOnly); if (!saveToDevice(&s)) { warnPigment << "saving palette failed:" << name(); return QByteArray(); } s.close(); s.open(QIODevice::ReadOnly); QByteArray res = s.readAll(); s.close(); return res; } bool KoColorSet::fromByteArray(QByteArray &data) { QBuffer buf(&data); buf.open(QIODevice::ReadOnly); return loadFromDevice(&buf); } KoColorSet::PaletteType KoColorSet::paletteType() const { return d->paletteType; } void KoColorSet::setPaletteType(PaletteType paletteType) { d->paletteType = paletteType; QString suffix; switch(d->paletteType) { case GPL: suffix = ".gpl"; break; case ACT: suffix = ".act"; break; case RIFF_PAL: case PSP_PAL: suffix = ".pal"; break; case ACO: suffix = ".aco"; break; case XML: suffix = ".xml"; break; case KPL: suffix = ".kpl"; break; case SBZ: suffix = ".sbz"; break; default: suffix = defaultFileExtension(); } QStringList fileName = filename().split("."); fileName.last() = suffix.replace(".", ""); setFilename(fileName.join(".")); } quint32 KoColorSet::colorCount() const { int colorCount = 0; for (KisSwatchGroup &g : d->groups.values()) { colorCount += g.colorCount(); } return colorCount; } void KoColorSet::add(const KisSwatch &c, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.addEntry(c); } void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.setEntry(e, x, y); } void KoColorSet::clear() { d->groups.clear(); d->groupNames.clear(); d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup(); d->groupNames.append(GLOBAL_GROUP_NAME); } KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const { int yInGroup = y; + KisSwatch e; QString nameGroupFoundIn; for (const QString &groupName : d->groupNames) { if (yInGroup < d->groups[groupName].rowCount()) { nameGroupFoundIn = groupName; break; } else { yInGroup -= d->groups[groupName].rowCount(); } } - const KisSwatchGroup &groupFoundIn = nameGroupFoundIn == GLOBAL_GROUP_NAME - ? d->global() : d->groups[nameGroupFoundIn]; - Q_ASSERT(groupFoundIn.checkEntry(x, yInGroup)); - return groupFoundIn.getEntry(x, yInGroup); + KisSwatchGroup &groupFoundIn = d->global(); + if (nameGroupFoundIn != GLOBAL_GROUP_NAME) { + groupFoundIn = d->groups[nameGroupFoundIn]; + } + if (groupFoundIn.checkEntry(x, yInGroup)) { + e = groupFoundIn.getEntry(x, yInGroup); + } + return e; } KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName) { KisSwatch e; const KisSwatchGroup &sourceGroup = groupName == QString() ? d->global() : d->groups[groupName]; if (sourceGroup.checkEntry(x, y)) { e = sourceGroup.getEntry(x, y); } return e; } QStringList KoColorSet::getGroupNames() { if (d->groupNames.size() != d->groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName) { if (!d->groups.contains(oldGroupName)) { return false; } if (oldGroupName == newGroupName) { return true; } d->groups[newGroupName] = d->groups[oldGroupName]; d->groups.remove(oldGroupName); d->groups[newGroupName].setName(newGroupName); //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } void KoColorSet::setColumnCount(int columns) { d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns); for (KisSwatchGroup &g : d->groups.values()) { g.setColumnCount(columns); } } int KoColorSet::columnCount() const { return d->groups[GLOBAL_GROUP_NAME].columnCount(); } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName] = KisSwatchGroup(); d->groups[groupName].setName(groupName); return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) { d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.indexOf(groupNameInsertBefore); d->groupNames.insert(index, groupName); } return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (groupName == GLOBAL_GROUP_NAME) { return false; } if (keepColors) { // put all colors directly below global int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount(); for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) { d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch, info.column, info.row + startingRow); } } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } int KoColorSet::rowCount() const { int res = 0; for (const QString &name : d->groupNames) { res += d->groups[name].rowCount(); } return res; } KisSwatchGroup *KoColorSet::getGroup(const QString &name) { if (!d->groups.contains(name)) { return 0; } return &(d->groups[name]); } KisSwatchGroup *KoColorSet::getGlobalGroup() { return getGroup(GLOBAL_GROUP_NAME); } bool KoColorSet::isGlobal() const { return d->isGlobal; } void KoColorSet::setIsGlobal(bool isGlobal) { d->isGlobal = isGlobal; } bool KoColorSet::isEditable() const { return d->isEditable; } void KoColorSet::setIsEditable(bool isEditable) { d->isEditable = isEditable; } KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace) { KisSwatchGroup::SwatchInfo res; quint8 highestPercentage = 0; quint8 testPercentage = 0; for (const QString &groupName : getGroupNames()) { KisSwatchGroup *group = getGroup(groupName); for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) { KoColor color = currInfo.swatch.color(); if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) { color.convertTo(compare.colorSpace()); } else if (compare.colorSpace() != color.colorSpace()) { compare.convertTo(color.colorSpace()); } testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data())); if (testPercentage > highestPercentage) { highestPercentage = testPercentage; res = currInfo; } } } return res; } /********************************KoColorSet::Private**************************/ KoColorSet::Private::Private(KoColorSet *a_colorSet) : colorSet(a_colorSet) , isGlobal(true) , isEditable(false) { groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); } KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KisSwatch colorEntry; // It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorName = colorProperties.value("NAME"); colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString()); // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8()); QStringRef colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.data()[0] = r; currentColor.data()[1] = g; currentColor.data()[2] = b; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); QStringRef colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.data()[0] = c; currentColor.data()[1] = m; currentColor.data()[2] = y; currentColor.data()[3] = k; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + colorEntry.name()); } } bool KoColorSet::Private::loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } quint16 KoColorSet::Private::readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::Private::init() { // just in case this is a reload (eg by KoEditColorSetDialog), groupNames.clear(); groups.clear(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); if (colorSet->filename().isNull()) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set"; return false; } if (data.isNull()) { QFile file(colorSet->filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); data = file.readAll(); file.close(); } bool res = false; paletteType = detectFormat(colorSet->filename(), data); switch(paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } colorSet->setValid(res); QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); for (const KisSwatchGroup::SwatchInfo &info : global().infoList()) { QColor c = info.swatch.color().toQColor(); gc.fillRect(info.column * 4, info.row * 4, 4, 4, c); } colorSet->setImage(img); colorSet->setValid(res); data.clear(); return res; } bool KoColorSet::Private::saveGpl(QIODevice *dev) const { Q_ASSERT(dev->isOpen()); Q_ASSERT(dev->isWritable()); QTextStream stream(dev); stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n"; /* * Qt doesn't provide an interface to get a const reference to a QHash, that is * the underlying data structure of groups. Therefore, directly use * groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const */ for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) { for (int x = 0; x < colorSet->columnCount(); x++) { if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) { continue; } const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y); QColor c = entry.color().toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; if (entry.name().isEmpty()) stream << "Untitled\n"; else stream << entry.name() << "\n"; } } return true; } bool KoColorSet::Private::loadGpl() { if (data.isEmpty() || data.isNull() || data.length() < 50) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } quint32 index = 0; QStringList lines = readAllLinesSafe(&data); if (lines.size() < 3) { warnPigment << "Not enough lines in palette file: " << colorSet->filename(); return false; } QString columnsText; qint32 r, g, b; KisSwatch e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1())); index = 2; // Read columns int columns = 0; if (lines[index].toLower().contains("columns")) { columnsText = lines[index].split(":")[1].trimmed(); columns = columnsText.toInt(); if (columns > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead"; global().setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { global().setColumnCount(columns); } index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); for (int i = 0; i != 3; i++) { a.pop_front(); } QString name = a.join(" "); e.setName(name.isEmpty() ? i18n("Untitled") : name); global().addEntry(e); } } int rowCount = global().colorCount()/ global().columnCount(); if (global().colorCount() % global().columnCount()>0) { rowCount ++; } global().setRowCount(rowCount); return true; } bool KoColorSet::Private::loadAct() { QFileInfo info(colorSet->filename()); - colorSet->setName(info.baseName()); + colorSet->setName(info.completeBaseName()); KisSwatch e; for (int i = 0; i < data.size(); i += 3) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); global().addEntry(e); } return true; } bool KoColorSet::Private::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(colorSet->filename()); - colorSet->setName(info.baseName()); + colorSet->setName(info.completeBaseName()); KisSwatch e; RiffHeader header; memcpy(&header, data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < data.size()); i += 4) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadPsp() { QFileInfo info(colorSet->filename()); - colorSet->setName(info.baseName()); + colorSet->setName(info.completeBaseName()); KisSwatch e; qint32 r, g, b; QStringList l = readAllLinesSafe(&data); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); QString name = a.join(" "); e.setName(name.isEmpty() ? i18n("Untitled") : name); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadKpl() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) { return false; } if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG); while (!c.isNull()) { QString name = c.attribute(KPL_PALETTE_NAME_ATTR); QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR); QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR); QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); int desiredColumnCount; QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR)); colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true"); comment = e.attribute(KPL_PALETTE_COMMENT_ATTR); desiredColumnCount = e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt(); if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount << ") in KPL palette file " << colorSet->filename() << " - setting maximum allowed column count instead."; colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { colorSet->setColumnCount(desiredColumnCount); } loadKplGroup(doc, e, colorSet->getGlobalGroup()); QDomElement g = e.firstChildElement(KPL_GROUP_TAG); while (!g.isNull()) { QString groupName = g.attribute(KPL_GROUP_NAME_ATTR); colorSet->addGroup(groupName); loadKplGroup(doc, g, colorSet->getGroup(groupName)); g = g.nextSiblingElement(KPL_GROUP_TAG); } } buf.close(); return true; } bool KoColorSet::Private::loadAco() { QFileInfo info(colorSet->filename()); - colorSet->setName(info.baseName()); + colorSet->setName(info.completeBaseName()); QBuffer buf(&data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KisSwatch e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 1) { // HSB QColor qc; qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16()); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 2) { // CMYK KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = quint16_MAX - ch1; reinterpret_cast(c.data())[1] = quint16_MAX - ch2; reinterpret_cast(c.data())[2] = quint16_MAX - ch3; reinterpret_cast(c.data())[3] = quint16_MAX - ch4; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 7) { // LAB KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 8) { // GRAY KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = ch1 * (quint16_MAX / 10000); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else { warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.setName(Utf16Codec->toUnicode(ba)); } else { warnPigment << "Version 2 name block is the wrong size" << colorSet->filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } } return true; } bool KoColorSet::Private::loadSbz() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << colorSet->filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; colorSet->setName(colorName); dbgPigment << "Processed name of palette:" << colorSet->name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { KisSwatch currentEntry; // Set if color is spot currentEntry.setSpotColor(colorElement.attribute("usage") == "spot"); // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name currentEntry.setId(colorId.text()); currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text()); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = l; reinterpret_cast(c.data())[1] = a; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; currentEntry.setColor(c); break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = x; reinterpret_cast(c.data())[1] = y; reinterpret_cast(c.data())[2] = z; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor color(colorSpace); reinterpret_cast(color.data())[0] = c; reinterpret_cast(color.data())[1] = m; reinterpret_cast(color.data())[2] = y; reinterpret_cast(color.data())[3] = k; color.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(color); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = g; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentEntry.id(), currentEntry); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { groups[currentGroupName].addEntry(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } bool KoColorSet::Private::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format"; res = loadScribusXmlPalette(colorSet, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << colorSet->filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << colorSet->filename(); return true; } } bool KoColorSet::Private::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) return false; QSet colorSpaces; { QDomDocument doc; QDomElement root = doc.createElement(KPL_PALETTE_TAG); root.setAttribute(KPL_VERSION_ATTR, "1.0"); root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name()); root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment); root.setAttribute(KPL_PALETTE_READONLY_ATTR, (colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true"); root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount()); root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount()); saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces); for (const QString &groupName : groupNames) { if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { continue; } QDomElement gl = doc.createElement(KPL_GROUP_TAG); gl.setAttribute(KPL_GROUP_NAME_ATTR, groupName); root.appendChild(gl); saveKplGroup(doc, gl, colorSet->getGroup(groupName), colorSpaces); } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); for (const KoColorSpace *colorSpace : colorSpaces) { QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = colorSpace->profile()->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG); el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn); el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name()); el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id()); el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } void KoColorSet::Private::saveKplGroup(QDomDocument &doc, QDomElement &groupEle, const KisSwatchGroup *group, QSet &colorSetSet) const { groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount())); for (const SwatchInfoType &info : group->infoList()) { const KoColorProfile *profile = info.swatch.color().colorSpace()->profile(); // Only save non-builtin profiles.= if (!profile->fileName().isEmpty()) { colorSetSet.insert(info.swatch.color().colorSpace()); } QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG); swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name()); swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id()); swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false"); swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id()); info.swatch.color().toXML(doc, swatchEle); QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG); positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row); positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column); swatchEle.appendChild(positionEle); groupEle.appendChild(swatchEle); } } void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroup *group) { Q_UNUSED(doc); if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) { group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt()); } group->setColumnCount(colorSet->columnCount()); for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG); !swatchEle.isNull(); swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) { QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id()); KisSwatch entry; entry.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId)); entry.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR)); entry.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR)); entry.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false); QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG); if (!positionEle.isNull()) { int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt(); int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt(); if (columnNumber < 0 || columnNumber >= colorSet->columnCount() || rowNumber < 0 ) { warnPigment << "Swatch" << entry.name() << "of palette" << colorSet->name() << "has invalid position."; continue; } group->setEntry(entry, columnNumber, rowNumber); } else { group->addEntry(entry); } } if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull() && group->colorCount() > 0 && group->columnCount() > 0 && (group->colorCount() / (group->columnCount()) + 1) < 20) { group->setRowCount((group->colorCount() / group->columnCount()) + 1); } } diff --git a/libs/pigment/resources/KoStopGradient.cpp b/libs/pigment/resources/KoStopGradient.cpp index a49a173aec..ac583e733e 100644 --- a/libs/pigment/resources/KoStopGradient.cpp +++ b/libs/pigment/resources/KoStopGradient.cpp @@ -1,612 +1,604 @@ /* Copyright (C) 2005 Tim Beaulen Copyright (C) 2007 Jan Hambrecht Copyright (c) 2007 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoMixColorsOp.h" #include "kis_dom_utils.h" #include #include KoStopGradient::KoStopGradient(const QString& filename) : KoAbstractGradient(filename) { } KoStopGradient::~KoStopGradient() { } bool KoStopGradient::operator==(const KoStopGradient &rhs) const { return *colorSpace() == *rhs.colorSpace() && spread() == rhs.spread() && type() == rhs.type() && m_start == rhs.m_start && m_stop == rhs.m_stop && m_focalPoint == rhs.m_focalPoint && m_stops == rhs.m_stops; } KoAbstractGradient* KoStopGradient::clone() const { return new KoStopGradient(*this); } bool KoStopGradient::load() { QFile f(filename()); if (!f.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&f); f.close(); return res; } bool KoStopGradient::loadFromDevice(QIODevice *dev) { QString strExt; const int result = filename().lastIndexOf('.'); if (result >= 0) { strExt = filename().mid(result).toLower(); } QByteArray ba = dev->readAll(); QBuffer buf(&ba); loadSvgGradient(&buf); if (m_stops.count() >= 2) { setValid(true); } updatePreview(); return true; } bool KoStopGradient::save() { QFile fileOut(filename()); if (! fileOut.open(QIODevice::WriteOnly)) return false; bool retval = saveToDevice(&fileOut); fileOut.close(); return retval; } QGradient* KoStopGradient::toQGradient() const { QGradient* gradient; switch (type()) { case QGradient::LinearGradient: { gradient = new QLinearGradient(m_start, m_stop); break; } case QGradient::RadialGradient: { QPointF diff = m_stop - m_start; qreal radius = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); gradient = new QRadialGradient(m_start, radius, m_focalPoint); break; } case QGradient::ConicalGradient: { qreal angle = atan2(m_start.y(), m_start.x()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; gradient = new QConicalGradient(m_start, angle); break; } default: return 0; } QColor color; for (QList::const_iterator i = m_stops.begin(); i != m_stops.end(); ++i) { i->second.toQColor(&color); gradient->setColorAt(i->first , color); } gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setSpread(this->spread()); return gradient; } bool KoStopGradient::stopsAt(KoGradientStop &leftStop, KoGradientStop &rightStop, qreal t) const { if (! m_stops.count()) return false; if (t <= m_stops.first().first || m_stops.count() == 1) { // we have only one stop or t is before the first stop leftStop = m_stops.first(); rightStop = KoGradientStop(-std::numeric_limits::infinity(), leftStop.second); return true; } else if (t >= m_stops.last().first) { // t is after the last stop rightStop = m_stops.last(); leftStop = KoGradientStop(std::numeric_limits::infinity(), rightStop.second); return true; } else { // we have at least two color stops // -> find the two stops which frame our t - QList::const_iterator stop = m_stops.begin(); - QList::const_iterator lastStop = m_stops.end(); - // we already checked the first stop, so we start at the second - for (++stop; stop != lastStop; ++stop) { - // we break at the stop which is just after our t - if (stop->first > t) - break; - } - - leftStop = *(stop - 1); - rightStop = *(stop); + auto it = std::lower_bound(m_stops.begin(), m_stops.end(), KoGradientStop(t, KoColor()), [](const KoGradientStop &a, const KoGradientStop &b){ + return a.first < b.first; + }); + leftStop = *(it - 1); + rightStop = *(it); return true; } } void KoStopGradient::colorAt(KoColor& dst, qreal t) const { KoColor buffer; KoGradientStop leftStop, rightStop; if (!stopsAt(leftStop, rightStop, t)) return; const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile()); KoColor startDummy, endDummy; if (mixSpace){ startDummy = KoColor(leftStop.second, mixSpace); endDummy = KoColor(rightStop.second, mixSpace); } else { startDummy = leftStop.second; endDummy = rightStop.second; } const quint8 *colors[2]; colors[0] = startDummy.data(); colors[1] = endDummy.data(); qreal localT; qreal stopDistance = rightStop.first - leftStop.first; if (stopDistance < DBL_EPSILON) { localT = 0.5; } else { localT = (t - leftStop.first) / stopDistance; } qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - localT) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; - //check if our mixspace exists, it doesn't at startup. if (mixSpace){ if (*buffer.colorSpace() != *mixSpace) { buffer = KoColor(mixSpace); } mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } else { buffer = KoColor(colorSpace()); colorSpace()->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } - dst.fromKoColor(buffer); } KoStopGradient * KoStopGradient::fromQGradient(const QGradient * gradient) { if (! gradient) return 0; KoStopGradient * newGradient = new KoStopGradient(QString()); newGradient->setType(gradient->type()); newGradient->setSpread(gradient->spread()); switch (gradient->type()) { case QGradient::LinearGradient: { const QLinearGradient * g = static_cast(gradient); newGradient->m_start = g->start(); newGradient->m_stop = g->finalStop(); newGradient->m_focalPoint = g->start(); break; } case QGradient::RadialGradient: { const QRadialGradient * g = static_cast(gradient); newGradient->m_start = g->center(); newGradient->m_stop = g->center() + QPointF(g->radius(), 0); newGradient->m_focalPoint = g->focalPoint(); break; } case QGradient::ConicalGradient: { const QConicalGradient * g = static_cast(gradient); qreal radian = g->angle() * M_PI / 180.0; newGradient->m_start = g->center(); newGradient->m_stop = QPointF(100.0 * cos(radian), 100.0 * sin(radian)); newGradient->m_focalPoint = g->center(); break; } default: delete newGradient; return 0; } Q_FOREACH (const QGradientStop & stop, gradient->stops()) { KoColor color(newGradient->colorSpace()); color.fromQColor(stop.second); newGradient->m_stops.append(KoGradientStop(stop.first, color)); } newGradient->setValid(true); return newGradient; } void KoStopGradient::setStops(QList< KoGradientStop > stops) { m_stops.clear(); KoColor color; Q_FOREACH (const KoGradientStop & stop, stops) { color = stop.second; color.convertTo(colorSpace()); m_stops.append(KoGradientStop(stop.first, color)); } updatePreview(); } QList KoStopGradient::stops() const { return m_stops; } void KoStopGradient::loadSvgGradient(QIODevice *file) { QDomDocument doc; if (!(doc.setContent(file))) file->close(); else { for (QDomNode n = doc.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) continue; if (e.tagName() == "linearGradient" || e.tagName() == "radialGradient") { parseSvgGradient(e); return; } // Inkscape gradients are in another defs if (e.tagName() == "defs") { for (QDomNode defnode = e.firstChild(); !defnode.isNull(); defnode = defnode.nextSibling()) { QDomElement defelement = defnode.toElement(); if (defelement.isNull()) continue; if (defelement.tagName() == "linearGradient" || defelement.tagName() == "radialGradient") { parseSvgGradient(defelement); return; } } } } } } void KoStopGradient::parseSvgGradient(const QDomElement& element) { m_stops.clear(); setSpread(QGradient::PadSpread); /*QString href = e.attribute( "xlink:href" ).mid( 1 ); if( !href.isEmpty() ) { }*/ setName(element.attribute("id", i18n("SVG Gradient"))); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); bool bbox = element.attribute("gradientUnits") != "userSpaceOnUse"; if (element.tagName() == "linearGradient") { if (bbox) { QString s; s = element.attribute("x1", "0%"); qreal xOrigin; if (s.endsWith('%')) xOrigin = s.remove('%').toDouble(); else xOrigin = s.toDouble() * 100.0; s = element.attribute("y1", "0%"); qreal yOrigin; if (s.endsWith('%')) yOrigin = s.remove('%').toDouble(); else yOrigin = s.toDouble() * 100.0; s = element.attribute("x2", "100%"); qreal xVector; if (s.endsWith('%')) xVector = s.remove('%').toDouble(); else xVector = s.toDouble() * 100.0; s = element.attribute("y2", "0%"); qreal yVector; if (s.endsWith('%')) yVector = s.remove('%').toDouble(); else yVector = s.toDouble() * 100.0; m_start = QPointF(xOrigin, yOrigin); m_stop = QPointF(xVector, yVector); } else { m_start = QPointF(element.attribute("x1").toDouble(), element.attribute("y1").toDouble()); m_stop = QPointF(element.attribute("x2").toDouble(), element.attribute("y2").toDouble()); } setType(QGradient::LinearGradient); } else { if (bbox) { QString s; s = element.attribute("cx", "50%"); qreal xOrigin; if (s.endsWith('%')) xOrigin = s.remove('%').toDouble(); else xOrigin = s.toDouble() * 100.0; s = element.attribute("cy", "50%"); qreal yOrigin; if (s.endsWith('%')) yOrigin = s.remove('%').toDouble(); else yOrigin = s.toDouble() * 100.0; s = element.attribute("cx", "50%"); qreal xVector; if (s.endsWith('%')) xVector = s.remove('%').toDouble(); else xVector = s.toDouble() * 100.0; s = element.attribute("r", "50%"); if (s.endsWith('%')) xVector += s.remove('%').toDouble(); else xVector += s.toDouble() * 100.0; s = element.attribute("cy", "50%"); qreal yVector; if (s.endsWith('%')) yVector = s.remove('%').toDouble(); else yVector = s.toDouble() * 100.0; s = element.attribute("fx", "50%"); qreal xFocal; if (s.endsWith('%')) xFocal = s.remove('%').toDouble(); else xFocal = s.toDouble() * 100.0; s = element.attribute("fy", "50%"); qreal yFocal; if (s.endsWith('%')) yFocal = s.remove('%').toDouble(); else yFocal = s.toDouble() * 100.0; m_start = QPointF(xOrigin, yOrigin); m_stop = QPointF(xVector, yVector); m_focalPoint = QPointF(xFocal, yFocal); } else { m_start = QPointF(element.attribute("cx").toDouble(), element.attribute("cy").toDouble()); m_stop = QPointF(element.attribute("cx").toDouble() + element.attribute("r").toDouble(), element.attribute("cy").toDouble()); m_focalPoint = QPointF(element.attribute("fx").toDouble(), element.attribute("fy").toDouble()); } setType(QGradient::RadialGradient); } // handle spread method QString spreadMethod = element.attribute("spreadMethod"); if (!spreadMethod.isEmpty()) { if (spreadMethod == "reflect") setSpread(QGradient::ReflectSpread); else if (spreadMethod == "repeat") setSpread(QGradient::RepeatSpread); } for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement colorstop = n.toElement(); if (colorstop.tagName() == "stop") { qreal opacity = 0.0; QColor c; float off; QString temp = colorstop.attribute("offset"); if (temp.contains('%')) { temp = temp.left(temp.length() - 1); off = temp.toFloat() / 100.0; } else off = temp.toFloat(); if (!colorstop.attribute("stop-color").isEmpty()) parseSvgColor(c, colorstop.attribute("stop-color")); else { // try style attr QString style = colorstop.attribute("style").simplified(); QStringList substyles = style.split(';', QString::SkipEmptyParts); Q_FOREACH (const QString & s, substyles) { QStringList substyle = s.split(':'); QString command = substyle[0].trimmed(); QString params = substyle[1].trimmed(); if (command == "stop-color") parseSvgColor(c, params); if (command == "stop-opacity") opacity = params.toDouble(); } } if (!colorstop.attribute("stop-opacity").isEmpty()) opacity = colorstop.attribute("stop-opacity").toDouble(); KoColor color(rgbColorSpace); color.fromQColor(c); color.setOpacity(static_cast(opacity * OPACITY_OPAQUE_U8 + 0.5)); //According to the SVG spec each gradient offset has to be equal to or greater than the previous one //if not it needs to be adjusted to be equal if (m_stops.count() > 0 && m_stops.last().first >= off) { off = m_stops.last().first; } m_stops.append(KoGradientStop(off, color)); } } } void KoStopGradient::parseSvgColor(QColor &color, const QString &s) { if (s.startsWith("rgb(")) { QString parse = s.trimmed(); QStringList colors = parse.split(','); QString r = colors[0].right((colors[0].length() - 4)); QString g = colors[1]; QString b = colors[2].left((colors[2].length() - 1)); if (r.contains('%')) { r = r.left(r.length() - 1); r = QString::number(int((qreal(255 * r.toDouble()) / 100.0))); } if (g.contains('%')) { g = g.left(g.length() - 1); g = QString::number(int((qreal(255 * g.toDouble()) / 100.0))); } if (b.contains('%')) { b = b.left(b.length() - 1); b = QString::number(int((qreal(255 * b.toDouble()) / 100.0))); } color = QColor(r.toInt(), g.toInt(), b.toInt()); } else { QString rgbColor = s.trimmed(); QColor c; if (rgbColor.startsWith('#')) c.setNamedColor(rgbColor); else { c = QColor(rgbColor); } color = c; } } QString KoStopGradient::defaultFileExtension() const { return QString(".svg"); } void KoStopGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const { gradientElt.setAttribute("type", "stop"); for (int s = 0; s < m_stops.size(); s++) { KoGradientStop stop = m_stops.at(s); QDomElement stopElt = doc.createElement("stop"); stopElt.setAttribute("offset", KisDomUtils::toString(stop.first)); stopElt.setAttribute("bitdepth", stop.second.colorSpace()->colorDepthId().id()); stopElt.setAttribute("alpha", KisDomUtils::toString(stop.second.opacityF())); stop.second.toXML(doc, stopElt); gradientElt.appendChild(stopElt); } } KoStopGradient KoStopGradient::fromXML(const QDomElement &elt) { KoStopGradient gradient; QList stops; QDomElement stopElt = elt.firstChildElement("stop"); while (!stopElt.isNull()) { qreal offset = KisDomUtils::toDouble(stopElt.attribute("offset", "0.0")); QString bitDepth = stopElt.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColor color = KoColor::fromXML(stopElt.firstChildElement(), bitDepth); color.setOpacity(KisDomUtils::toDouble(stopElt.attribute("alpha", "1.0"))); stops.append(KoGradientStop(offset, color)); stopElt = stopElt.nextSiblingElement("stop"); } gradient.setStops(stops); return gradient; } bool KoStopGradient::saveToDevice(QIODevice *dev) const { QTextStream stream(dev); const QString spreadMethod[3] = { QString("spreadMethod=\"pad\" "), QString("spreadMethod=\"reflect\" "), QString("spreadMethod=\"repeat\" ") }; const QString indent = " "; stream << "" << endl; stream << indent; stream << "" << endl; QColor color; // color stops Q_FOREACH (const KoGradientStop & stop, m_stops) { stop.second.toQColor(&color); stream << indent << indent; stream << "(color.alpha()) / 255.0f << "\"" << " />" << endl; } stream << indent; stream << "" << endl; stream << "" << endl; KoResource::saveToDevice(dev); return true; } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 03ae2034c9..4625189838 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,616 +1,616 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) 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 canvas/KisSnapPixelStrategy.cpp canvas/KisMirrorAxisConfig.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/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp dialogs/KisDlgChangeCloneSource.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 KisPaintopPropertiesBase.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 KisOcioConfiguration.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 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 KisNodeDisplayModeAdapter.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 KisDecorationsManager.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 KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp KisChangeCloneLayersCommand.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 opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp 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_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 tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.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_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_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/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.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_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp widgets/KisDitherWidget.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp utils/KisClipboardUtil.cpp utils/KisDitherUtil.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 input/KisInputActionGroup.cpp + input/kis_zoom_and_rotate_action.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactories.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.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 KisCloneDocumentStroke.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 KisUndoActionsUpdateManager.cpp KisView.cpp KisImportExportErrorCode.cpp KisImportExportAdditionalChecks.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 KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h KisMouseClickEater.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() 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 KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) 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 widgets/KisDitherWidget.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/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 forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui forms/wdgchangeclonesource.ui brushhud/kis_dlg_brush_hud_config.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 layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() 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} LibExiv2::LibExiv2 ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) 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/KisFilterEntry.cpp b/libs/ui/KisFilterEntry.cpp deleted file mode 100644 index deadd33277..0000000000 --- a/libs/ui/KisFilterEntry.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 1998, 1999 Torben Weis - Copyright 2007 David Faure - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public License -along with this library; 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 "KisFilterEntry.h" - -#include "KisDocument.h" -#include "KisImportExportFilter.h" - -#include -#include -#include -#include -#include // UINT_MAX - - -KisFilterEntry::KisFilterEntry(QPluginLoader *loader) - : KisShared() - , m_loader(loader) -{ - import = loader->metaData().value("MetaData").toObject().value("X-KDE-Import").toString().split(','); - export_ = loader->metaData().value("MetaData").toObject().value("X-KDE-Export").toString().split(','); - int w = loader->metaData().value("MetaData").toObject().value("X-KDE-Weight").toString().toInt(); - weight = w < 0 ? UINT_MAX : static_cast(w); - available = loader->metaData().value("MetaData").toObject().value("X-KDE-Available").toString(); -} - -KisFilterEntry::~KisFilterEntry() -{ - delete m_loader; -} - -QList KisFilterEntry::query() -{ - QList lst; - - QList offers = KoJsonTrader::instance()->query("Krita/FileFilter", QString()); - unsigned int max = offers.count(); - dbgFile <<"Query returned" << max <<" offers"; - - Q_FOREACH(QPluginLoader *pluginLoader, offers) { - //dbgFile <<" desktopEntryPath=" << (*it)->entryPath() - // << " library=" << (*it)->library() << endl; - // Append converted offer - lst.append(KisFilterEntrySP(new KisFilterEntry(pluginLoader))); - } - return lst; -} - -KisImportExportFilter* KisFilterEntry::createFilter() -{ - KLibFactory *factory = qobject_cast(m_loader->instance()); - - if (!factory) { - warnUI << m_loader->errorString(); - return 0; - } - - QObject *obj = factory->create(0); - if (!obj || !obj->inherits("KisImportExportFilter")) { - delete obj; - return 0; - } - - KisImportExportFilter* filter = static_cast(obj); - return filter; -} - diff --git a/libs/ui/KisFilterEntry.h b/libs/ui/KisFilterEntry.h deleted file mode 100644 index b71a52b70d..0000000000 --- a/libs/ui/KisFilterEntry.h +++ /dev/null @@ -1,102 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 1998, 1999 Torben Weis - Copyright 2007 David Faure - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public License -along with this library; 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_FILTER_ENTRY_H -#define KIS_FILTER_ENTRY_H - -#include -#include - -#include "kis_types.h" -#include "kis_shared.h" -#include "kis_shared_ptr.h" - -#include "kritaui_export.h" - -class QObject; -class QPluginLoader; -class KisImportExportFilter; - -class KisFilterEntry; -typedef KisSharedPtr KisFilterEntrySP; - -/** - * Represents an available filter. - */ -class KRITAUI_EXPORT KisFilterEntry : public KisShared -{ - -public: - - //KisFilterEntry() : weight( 0 ) { m_service = 0; } // for QList - explicit KisFilterEntry(QPluginLoader *loader); - ~KisFilterEntry(); - - KisImportExportFilter *createFilter(); - - /** - * The imported mimetype(s). - */ - QStringList import; - - /** - * The exported mimetype(s). - */ - QStringList export_; - - /** - * The "weight" of this filter path. Has to be > 0 to be valid. - */ - unsigned int weight; - - /** - * Do we have to check during runtime? - */ - QString available; - - /** - * @return TRUE if the filter can import the requested mimetype. - */ - bool imports(const QString& _mimetype) const { - return (import.contains(_mimetype)); - } - - /** - * @return TRUE if the filter can export the requested mimetype. - */ - bool exports(const QString& _m) const { - return (export_.contains(_m)); - } - - /** - * This function will query KDED to find all available filters. - */ - static QList query(); - - QPluginLoader *loader() const { - return m_loader; - } - -private: - QPluginLoader *m_loader; -}; - - -#endif diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp index 65eaf8dedc..baabd4875c 100644 --- a/libs/ui/KisImportExportFilter.cpp +++ b/libs/ui/KisImportExportFilter.cpp @@ -1,344 +1,344 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisImportExportFilter.h" #include #include #include #include #include "KisImportExportManager.h" #include #include #include #include #include "KoUpdater.h" #include #include "kis_config.h" #include #include const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency"; const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID"; const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID"; const QString KisImportExportFilter::sRGBTag = "sRGB"; class Q_DECL_HIDDEN KisImportExportFilter::Private { public: QPointer updater; QByteArray mime; QString filename; QString realFilename; bool batchmode; QMap capabilities; Private() : updater(0), mime("") , batchmode(false) {} ~Private() { qDeleteAll(capabilities); } }; KisImportExportFilter::KisImportExportFilter(QObject *parent) : QObject(parent) , d(new Private) { } KisImportExportFilter::~KisImportExportFilter() { if (d->updater) { d->updater->setProgress(100); } delete d; } QString KisImportExportFilter::filename() const { return d->filename; } QString KisImportExportFilter::realFilename() const { return d->realFilename; } bool KisImportExportFilter::batchMode() const { return d->batchmode; } void KisImportExportFilter::setBatchMode(bool batchmode) { d->batchmode = batchmode; } void KisImportExportFilter::setFilename(const QString &filename) { d->filename = filename; } void KisImportExportFilter::setRealFilename(const QString &filename) { d->realFilename = filename; } void KisImportExportFilter::setMimeType(const QString &mime) { d->mime = mime.toLatin1(); } QByteArray KisImportExportFilter::mimeType() const { return d->mime; } QString KisImportExportFilter::conversionStatusString(ConversionStatus status) { QString msg; switch (status) { case OK: break; case FilterCreationError: msg = i18n("Krita does not support this file format"); break; case CreationError: msg = i18n("Could not create the output document"); break; case FileNotFound: msg = i18n("File not found"); break; case StorageCreationError: msg = i18n("Cannot create storage"); break; case BadMimeType: msg = i18n("Bad MIME type"); break; case WrongFormat: msg = i18n("Format not recognized"); break; case NotImplemented: msg = i18n("Not implemented"); break; case ParsingError: msg = i18n("Parsing error"); break; case InvalidFormat: msg = i18n("Invalid file format"); break; case InternalError: case UsageError: msg = i18n("Internal error"); break; case ProgressCancelled: msg = i18n("Cancelled by user"); break; case BadConversionGraph: msg = i18n("Unknown file type"); break; case UnsupportedVersion: msg = i18n("Unsupported file version"); break; case UserCancelled: // intentionally we do not prompt the error message here break; default: msg = i18n("Unknown error"); break; } return msg; } KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); const QString filterConfig = KisConfig(true).exportConfigurationXML(to); if (cfg && !filterConfig.isEmpty()) { cfg->fromXML(filterConfig, false); } return cfg; } KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } QMap KisImportExportFilter::exportChecks() { qDeleteAll(d->capabilities); initializeCapabilities(); return d->capabilities; } QString KisImportExportFilter::verify(const QString &fileName) const { QFileInfo fi(fileName); if (!fi.exists()) { - return i18n("%1 does not exit after writing. Try saving again under a different name, in another location.", fileName); + return i18n("%1 does not exist after writing. Try saving again under a different name, in another location.", fileName); } if (!fi.isReadable()) { return i18n("%1 is not readable", fileName); } if (fi.size() < 10) { return i18n("%1 is smaller than 10 bytes, it must be corrupt. Try saving again under a different name, in another location.", fileName); } QFile f(fileName); f.open(QFile::ReadOnly); QByteArray ba = f.read(std::min(f.size(), (qint64)1000)); bool found = false; for(int i = 0; i < ba.size(); ++i) { if (ba.at(i) > 0) { found = true; break; } } if (!found) { return i18n("%1 has only zero bytes in the first 1000 bytes, it's probably corrupt. Try saving again under a different name, in another location.", fileName); } return QString(); } void KisImportExportFilter::setUpdater(QPointer updater) { d->updater = updater; } void KisImportExportFilter::setProgress(int value) { if (d->updater) { d->updater->setValue(value); } } void KisImportExportFilter::initializeCapabilities() { // XXX: Initialize everything to fully supported? } void KisImportExportFilter::addCapability(KisExportCheckBase *capability) { d->capabilities[capability->id()] = capability; } void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level) { Q_ASSERT(level != KisExportCheckBase::SUPPORTED); QString layerMessage; QString imageMessage; QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorModelID, allColorModels) { QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorDepthID, allColorDepths) { KisExportCheckFactory *colorModelCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id()); KisExportCheckFactory *colorModelPerLayerCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id()); if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) { qWarning() << "No factory for" << colorModelID << colorDepthID; continue; } if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) { addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED)); addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED)); } else { if (level == KisExportCheckBase::PARTIALLY) { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will be converted." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be converted or skipped." ,name, colorModelID.name(), colorDepthID.name()); } else { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will not be saved." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be skipped." , name, colorModelID.name(), colorDepthID.name()); } addCapability(colorModelCheckFactory->create(level, imageMessage)); addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage)); } } } } QString KisImportExportFilter::verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const { QScopedPointer store(KoStore::createStore(fileName, KoStore::Read, KIS_MIME_TYPE, KoStore::Zip)); if (!store || store->bad()) { return i18n("Could not open the saved file %1. Please try to save again in a different location.", fileName); } Q_FOREACH(const QString &file, filesToCheck) { if (!store->hasFile(file)) { return i18n("File %1 is missing in %2 and is broken. Please try to save again in a different location.", file, fileName); } } return QString(); } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 8aaa5b5d74..6977484d8a 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2652 +1,2660 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include "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 "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); - widgetStack->addWidget(welcomePage); + welcomeScroller = new QScrollArea(); + welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + welcomeScroller->setWidget(welcomePage); + welcomeScroller->setWidgetResizable(true); + + widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; + QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { 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) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In ths case we should clear oll the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { 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); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * 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(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } 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") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; 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; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header 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::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) - QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); + QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // 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(); + QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); - document->documentInfo()->setAboutInfo("title", info.baseName()); + document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); - QString filename = QFileInfo(document->url().toLocalFile()).baseName(); + QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) - && (QFileInfo(newURL.toLocalFile()).baseName() == filename) + && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { 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(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } 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->canvasResourceProvider()->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, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will 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(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); - title = info.baseName(); + title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = 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 compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } #include diff --git a/libs/ui/KisPart.cpp b/libs/ui/KisPart.cpp index 90a33f7047..bdd1bc6318 100644 --- a/libs/ui/KisPart.cpp +++ b/libs/ui/KisPart.cpp @@ -1,565 +1,565 @@ /* This file is part of the KDE project * Copyright (C) 1998-1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * Copyright (C) 2015 Michael Abrahams * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisPart.h" #include "KoProgressProxy.h" #include #include #include #include #include #include #include #include #include "KisApplication.h" #include "KisMainWindow.h" #include "KisDocument.h" #include "KisView.h" #include "KisViewManager.h" #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_shape_controller.h" #include "KisResourceServerProvider.h" #include "kis_animation_cache_populator.h" #include "kis_idle_watcher.h" #include "kis_image.h" #include "KisOpenPane.h" #include "kis_color_manager.h" #include "kis_action.h" #include "kis_action_registry.h" #include "KisSessionResource.h" Q_GLOBAL_STATIC(KisPart, s_instance) class Q_DECL_HIDDEN KisPart::Private { public: Private(KisPart *_part) : part(_part) , idleWatcher(2500) , animationCachePopulator(_part) { } ~Private() { } KisPart *part; QList > views; QList > mainWindows; QList > documents; KActionCollection *actionCollection{0}; KisIdleWatcher idleWatcher; KisAnimationCachePopulator animationCachePopulator; KisSessionResource *currentSession = nullptr; bool closingSession{false}; QScopedPointer sessionManager; bool queryCloseDocument(KisDocument *document) { Q_FOREACH(auto view, views) { if (view && view->isVisible() && view->document() == document) { return view->queryClose(); } } return true; } }; KisPart* KisPart::instance() { return s_instance; } KisPart::KisPart() : d(new Private(this)) { // Preload all the resources in the background Q_UNUSED(KoResourceServerProvider::instance()); Q_UNUSED(KisResourceServerProvider::instance()); Q_UNUSED(KisColorManager::instance()); connect(this, SIGNAL(documentOpened(QString)), this, SLOT(updateIdleWatcherConnections())); connect(this, SIGNAL(documentClosed(QString)), this, SLOT(updateIdleWatcherConnections())); connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()), this, SLOT(updateShortcuts())); connect(&d->idleWatcher, SIGNAL(startedIdleMode()), &d->animationCachePopulator, SLOT(slotRequestRegeneration())); d->animationCachePopulator.slotRequestRegeneration(); } KisPart::~KisPart() { while (!d->documents.isEmpty()) { delete d->documents.takeFirst(); } while (!d->views.isEmpty()) { delete d->views.takeFirst(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } delete d; } void KisPart::updateIdleWatcherConnections() { QVector images; Q_FOREACH (QPointer document, documents()) { if (document->image()) { images << document->image(); } } d->idleWatcher.setTrackedImages(images); } void KisPart::addDocument(KisDocument *document) { //dbgUI << "Adding document to part list" << document; Q_ASSERT(document); if (!d->documents.contains(document)) { d->documents.append(document); emit documentOpened('/'+objectName()); emit sigDocumentAdded(document); connect(document, SIGNAL(sigSavingFinished()), SLOT(slotDocumentSaved())); } } QList > KisPart::documents() const { return d->documents; } KisDocument *KisPart::createDocument() const { KisDocument *doc = new KisDocument(); return doc; } int KisPart::documentCount() const { return d->documents.size(); } void KisPart::removeDocument(KisDocument *document) { d->documents.removeAll(document); emit documentClosed('/'+objectName()); emit sigDocumentRemoved(document->url().toLocalFile()); document->deleteLater(); } KisMainWindow *KisPart::createMainWindow(QUuid id) { KisMainWindow *mw = new KisMainWindow(id); dbgUI <<"mainWindow" << (void*)mw << "added to view" << this; d->mainWindows.append(mw); emit sigWindowAdded(mw); return mw; } KisView *KisPart::createView(KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection, QWidget *parent) { // If creating the canvas fails, record this and disable OpenGL next time KisConfig cfg(false); KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention"); if (grp.readEntry("CreatingCanvas", false)) { - cfg.setUseOpenGL(false); + cfg.disableOpenGL(); } if (cfg.canvasState() == "OPENGL_FAILED") { - cfg.setUseOpenGL(false); + cfg.disableOpenGL(); } grp.writeEntry("CreatingCanvas", true); grp.sync(); QApplication::setOverrideCursor(Qt::WaitCursor); KisView *view = new KisView(document, resourceManager, actionCollection, parent); QApplication::restoreOverrideCursor(); // Record successful canvas creation grp.writeEntry("CreatingCanvas", false); grp.sync(); addView(view); return view; } void KisPart::addView(KisView *view) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } emit sigViewAdded(view); } void KisPart::removeView(KisView *view) { if (!view) return; /** * HACK ALERT: we check here explicitly if the document (or main * window), is saving the stuff. If we close the * document *before* the saving is completed, a crash * will happen. */ KIS_ASSERT_RECOVER_RETURN(!view->mainWindow()->hackIsSaving()); emit sigViewRemoved(view); QPointer doc = view->document(); d->views.removeAll(view); if (doc) { bool found = false; Q_FOREACH (QPointer view, d->views) { if (view && view->document() == doc) { found = true; break; } } if (!found) { removeDocument(doc); } } } QList > KisPart::views() const { return d->views; } int KisPart::viewCount(KisDocument *doc) const { if (!doc) { return d->views.count(); } else { int count = 0; Q_FOREACH (QPointer view, d->views) { if (view && view->isVisible() && view->document() == doc) { count++; } } return count; } } bool KisPart::closingSession() const { return d->closingSession; } bool KisPart::closeSession(bool keepWindows) { d->closingSession = true; Q_FOREACH(auto document, d->documents) { if (!d->queryCloseDocument(document.data())) { d->closingSession = false; return false; } } if (d->currentSession) { KisConfig kisCfg(false); if (kisCfg.saveSessionOnQuit(false)) { d->currentSession->storeCurrentWindows(); d->currentSession->save(); KConfigGroup cfg = KSharedConfig::openConfig()->group("session"); cfg.writeEntry("previousSession", d->currentSession->name()); } d->currentSession = nullptr; } if (!keepWindows) { Q_FOREACH (auto window, d->mainWindows) { window->close(); } if (d->sessionManager) { d->sessionManager->close(); } } d->closingSession = false; return true; } void KisPart::slotDocumentSaved() { KisDocument *doc = qobject_cast(sender()); emit sigDocumentSaved(doc->url().toLocalFile()); } void KisPart::removeMainWindow(KisMainWindow *mainWindow) { dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); } } const QList > &KisPart::mainWindows() const { return d->mainWindows; } int KisPart::mainwindowCount() const { return d->mainWindows.count(); } KisMainWindow *KisPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KisMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } KisMainWindow * KisPart::windowById(QUuid id) const { Q_FOREACH(QPointer mainWindow, d->mainWindows) { if (mainWindow->id() == id) { return mainWindow; } } return nullptr; } KisIdleWatcher* KisPart::idleWatcher() const { return &d->idleWatcher; } KisAnimationCachePopulator* KisPart::cachePopulator() const { return &d->animationCachePopulator; } void KisPart::openExistingFile(const QUrl &url) { // TODO: refactor out this method! KisMainWindow *mw = currentMainwindow(); KIS_SAFE_ASSERT_RECOVER_RETURN(mw); mw->openDocument(url, KisMainWindow::None); } void KisPart::updateShortcuts() { // Update any non-UI actionCollections. That includes: // Now update the UI actions. Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { KActionCollection *ac = mainWindow->actionCollection(); ac->updateShortcuts(); // Loop through mainWindow->actionCollections() to modify tooltips // so that they list shortcuts at the end in parentheses Q_FOREACH ( QAction* action, ac->actions()) { // Remove any existing suffixes from the tooltips. // Note this regexp starts with a space, e.g. " (Ctrl-a)" QString strippedTooltip = action->toolTip().remove(QRegExp("\\s\\(.*\\)")); // Now update the tooltips with the new shortcut info. if (action->shortcut() == QKeySequence(0)) action->setToolTip(strippedTooltip); else action->setToolTip( strippedTooltip + " (" + action->shortcut().toString() + ")"); } } } void KisPart::openTemplate(const QUrl &url) { qApp->setOverrideCursor(Qt::BusyCursor); KisDocument *document = createDocument(); bool ok = document->loadNativeFormat(url.toLocalFile()); document->setModified(false); document->undoStack()->clear(); if (ok) { QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); document->setMimeTypeAfterLoading(mimeType); document->resetURL(); document->setReadWrite(true); } else { if (document->errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath())); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage())); } delete document; return; } addDocument(document); KisMainWindow *mw = currentMainwindow(); mw->addViewAndNotifyLoadingCompleted(document); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } qApp->restoreOverrideCursor(); } void KisPart::addRecentURLToAllMainWindows(QUrl url) { // Add to recent actions list in our mainWindows Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(url); } } void KisPart::startCustomDocument(KisDocument* doc) { addDocument(doc); KisMainWindow *mw = currentMainwindow(); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } mw->addViewAndNotifyLoadingCompleted(doc); } KisInputManager* KisPart::currentInputManager() { KisMainWindow *mw = currentMainwindow(); KisViewManager *manager = mw ? mw->viewManager() : 0; return manager ? manager->inputManager() : 0; } void KisPart::showSessionManager() { if (d->sessionManager.isNull()) { d->sessionManager.reset(new KisSessionManagerDialog()); } d->sessionManager->show(); d->sessionManager->activateWindow(); } void KisPart::startBlankSession() { KisMainWindow *window = createMainWindow(); window->initializeGeometry(); window->show(); } bool KisPart::restoreSession(const QString &sessionName) { if (sessionName.isNull()) return false; KoResourceServer * rserver = KisResourceServerProvider::instance()->sessionServer(); auto *session = rserver->resourceByName(sessionName); if (!session || !session->valid()) return false; session->restore(); return true; } void KisPart::setCurrentSession(KisSessionResource *session) { d->currentSession = session; } diff --git a/libs/ui/KisReferenceImage.cpp b/libs/ui/KisReferenceImage.cpp index 20f91cc3b3..c7d1b195a8 100644 --- a/libs/ui/KisReferenceImage.cpp +++ b/libs/ui/KisReferenceImage.cpp @@ -1,359 +1,361 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisReferenceImage.h" #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include -struct KisReferenceImage::Private { +struct KisReferenceImage::Private : public QSharedData +{ // Filename within .kra (for embedding) QString internalFilename; // File on disk (for linking) QString externalFilename; QImage image; QImage cachedImage; KisQImagePyramid mipmap; qreal saturation{1.0}; int id{-1}; bool embed{true}; bool loadFromFile() { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!externalFilename.isEmpty(), false); return image.load(externalFilename); } bool loadFromClipboard() { image = KisClipboardUtil::getImageFromClipboard(); return !image.isNull(); } void updateCache() { if (saturation < 1.0) { cachedImage = KritaUtils::convertQImageToGrayA(image); if (saturation > 0.0) { QPainter gc2(&cachedImage); gc2.setOpacity(saturation); gc2.drawImage(QPoint(), image); } } else { cachedImage = image; } mipmap = KisQImagePyramid(cachedImage); } }; KisReferenceImage::SetSaturationCommand::SetSaturationCommand(const QList &shapes, qreal newSaturation, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Set saturation"), parent) , newSaturation(newSaturation) { images.reserve(shapes.count()); Q_FOREACH(auto *shape, shapes) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(reference); images.append(reference); } Q_FOREACH(auto *image, images) { oldSaturations.append(image->saturation()); } } void KisReferenceImage::SetSaturationCommand::undo() { auto saturationIterator = oldSaturations.begin(); Q_FOREACH(auto *image, images) { image->setSaturation(*saturationIterator); image->update(); saturationIterator++; } } void KisReferenceImage::SetSaturationCommand::redo() { Q_FOREACH(auto *image, images) { image->setSaturation(newSaturation); image->update(); } } KisReferenceImage::KisReferenceImage() : d(new Private()) { setKeepAspectRatio(true); } KisReferenceImage::KisReferenceImage(const KisReferenceImage &rhs) - : KoTosContainer(new KoTosContainerPrivate(*rhs.d_func(), this)) - , d(new Private(*rhs.d)) + : KoTosContainer(rhs) + , d(rhs.d) {} KisReferenceImage::~KisReferenceImage() {} KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent) { KisReferenceImage *reference = new KisReferenceImage(); reference->d->externalFilename = filename; bool ok = reference->d->loadFromFile(); if (ok) { QRect r = QRect(QPoint(), reference->d->image.size()); QSizeF shapeSize = converter.imageToDocument(r).size(); reference->setSize(shapeSize); } else { delete reference; if (parent) { QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename)); } return nullptr; } return reference; } KisReferenceImage *KisReferenceImage::fromClipboard(const KisCoordinatesConverter &converter) { KisReferenceImage *reference = new KisReferenceImage(); bool ok = reference->d->loadFromClipboard(); if (ok) { QRect r = QRect(QPoint(), reference->d->image.size()); QSizeF size = converter.imageToDocument(r).size(); reference->setSize(size); } else { delete reference; reference = nullptr; } return reference; } void KisReferenceImage::paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &/*paintcontext*/) { if (!parent()) return; gc.save(); applyConversion(gc, converter); QSizeF shapeSize = size(); QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height()); if (d->cachedImage.isNull()) { d->updateCache(); } qreal scale; QImage prescaled = d->mipmap.getClosest(gc.transform() * transform, &scale); transform.scale(1.0 / scale, 1.0 / scale); gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip); gc.setTransform(transform, true); gc.drawImage(QPoint(), prescaled); gc.restore(); } void KisReferenceImage::setSaturation(qreal saturation) { d->saturation = saturation; d->cachedImage = QImage(); } qreal KisReferenceImage::saturation() const { return d->saturation; } void KisReferenceImage::setEmbed(bool embed) { KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty()); d->embed = embed; } bool KisReferenceImage::embed() { return d->embed; } bool KisReferenceImage::hasLocalFile() { return !d->externalFilename.isEmpty(); } QString KisReferenceImage::filename() const { return d->externalFilename; } QString KisReferenceImage::internalFile() const { return d->internalFilename; } void KisReferenceImage::setFilename(const QString &filename) { d->externalFilename = filename; d->embed = false; } QColor KisReferenceImage::getPixel(QPointF position) { if (transparency() == 1.0) return Qt::transparent; const QSizeF shapeSize = size(); const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height()); const QTransform transform = absoluteTransformation(nullptr).inverted() * scale; const QPointF localPosition = position * transform; if (d->cachedImage.isNull()) { d->updateCache(); } return d->cachedImage.pixelColor(localPosition.toPoint()); } void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id) { d->id = id; QDomElement element = document.createElement("referenceimage"); if (d->embed) { d->internalFilename = QString("reference_images/%1.png").arg(id); } const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename); element.setAttribute("src", src); const QSizeF &shapeSize = size(); element.setAttribute("width", KisDomUtils::toString(shapeSize.width())); element.setAttribute("height", KisDomUtils::toString(shapeSize.height())); element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false"); element.setAttribute("transform", SvgUtil::transformToString(transform())); element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency())); element.setAttribute("saturation", KisDomUtils::toString(d->saturation)); parentElement.appendChild(element); } KisReferenceImage * KisReferenceImage::fromXml(const QDomElement &elem) { auto *reference = new KisReferenceImage(); const QString &src = elem.attribute("src"); if (src.startsWith("file://")) { reference->d->externalFilename = src.mid(7); reference->d->embed = false; } else { reference->d->internalFilename = src; reference->d->embed = true; } qreal width = KisDomUtils::toDouble(elem.attribute("width", "100")); qreal height = KisDomUtils::toDouble(elem.attribute("height", "100")); reference->setSize(QSizeF(width, height)); reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true"); auto transform = SvgTransformParser(elem.attribute("transform")).transform(); reference->setTransformation(transform); qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1")); reference->setTransparency(1.0 - opacity); qreal saturation = KisDomUtils::toDouble(elem.attribute("opacity", "1")); reference->setSaturation(saturation); return reference; } bool KisReferenceImage::saveImage(KoStore *store) const { if (!d->embed) return true; if (!store->open(d->internalFilename)) { return false; } bool saved = false; KoStoreDevice storeDev(store); if (storeDev.open(QIODevice::WriteOnly)) { saved = d->image.save(&storeDev, "PNG"); } return store->close() && saved; } bool KisReferenceImage::loadImage(KoStore *store) { if (!d->embed) { return d->loadFromFile(); } if (!store->open(d->internalFilename)) { return false; } KoStoreDevice storeDev(store); if (!storeDev.open(QIODevice::ReadOnly)) { return false; } if (!d->image.load(&storeDev, "PNG")) { return false; } return store->close(); } KoShape *KisReferenceImage::cloneShape() const { return new KisReferenceImage(*this); } diff --git a/libs/ui/KisReferenceImage.h b/libs/ui/KisReferenceImage.h index e315089574..c05ed517dd 100644 --- a/libs/ui/KisReferenceImage.h +++ b/libs/ui/KisReferenceImage.h @@ -1,97 +1,97 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISREFERENCEIMAGE_H #define KISREFERENCEIMAGE_H -#include +#include #include #include #include #include class QImage; class QPointF; class QPainter; class QRectF; class KoStore; class KisCoordinatesConverter; class KisCanvas2; /** * @brief The KisReferenceImage class represents a single reference image */ class KRITAUI_EXPORT KisReferenceImage : public KoTosContainer { public: struct KRITAUI_EXPORT SetSaturationCommand : public KUndo2Command { QVector images; QVector oldSaturations; qreal newSaturation; explicit SetSaturationCommand(const QList &images, qreal newSaturation, KUndo2Command *parent = 0); void undo() override; void redo() override; }; KisReferenceImage(); KisReferenceImage(const KisReferenceImage &rhs); ~KisReferenceImage(); KoShape *cloneShape() const override; /** * Load a reference image from specified file. * If parent is provided and the image cannot be loaded, a warning message will be displayed to user. * @return reference image or null if one could not be loaded */ static KisReferenceImage * fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent /*= nullptr*/); static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter); void setSaturation(qreal saturation); qreal saturation() const; void setEmbed(bool embed); bool embed(); bool hasLocalFile(); void setFilename(const QString &filename); QString filename() const; QString internalFile() const; void paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; bool loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/) override { return false; } void saveOdf(KoShapeSavingContext &/*context*/) const override {} QColor getPixel(QPointF position); void saveXml(QDomDocument &document, QDomElement &parentElement, int id); bool saveImage(KoStore *store) const; static KisReferenceImage * fromXml(const QDomElement &elem); bool loadImage(KoStore *store); private: struct Private; - const QScopedPointer d; + QSharedDataPointer d; }; #endif // KISREFERENCEIMAGE_H diff --git a/libs/ui/KisResourceBundle.cpp b/libs/ui/KisResourceBundle.cpp index 0af6e15521..00d501e2ae 100644 --- a/libs/ui/KisResourceBundle.cpp +++ b/libs/ui/KisResourceBundle.cpp @@ -1,1098 +1,1098 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 "KisResourceBundle.h" #include "KisResourceBundleManifest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisResourceBundle::KisResourceBundle(QString const& fileName) : KoResource(fileName), m_bundleVersion("1") { - setName(QFileInfo(fileName).baseName()); + setName(QFileInfo(fileName).completeBaseName()); m_metadata["generator"] = "Krita (" + KritaVersionWrapper::versionString(true) + ")"; } KisResourceBundle::~KisResourceBundle() { } QString KisResourceBundle::defaultFileExtension() const { return QString(".bundle"); } bool KisResourceBundle::load() { if (filename().isEmpty()) return false; QScopedPointer resourceStore(KoStore::createStore(filename(), KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip)); if (!resourceStore || resourceStore->bad()) { warnKrita << "Could not open store on bundle" << filename(); m_installed = false; setValid(false); return false; } else { m_metadata.clear(); bool toRecreate = false; if (resourceStore->open("META-INF/manifest.xml")) { if (!m_manifest.load(resourceStore->device())) { warnKrita << "Could not open manifest for bundle" << filename(); return false; } resourceStore->close(); Q_FOREACH (KisResourceBundleManifest::ResourceReference ref, m_manifest.files()) { if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Bundle is broken. File" << ref.resourcePath << "is missing"; toRecreate = true; } else { resourceStore->close(); } } if(toRecreate) { warnKrita << "Due to missing files and wrong entries in the manifest, " << filename() << " will be recreated."; } } else { warnKrita << "Could not load META-INF/manifest.xml"; return false; } bool versionFound = false; if (resourceStore->open("meta.xml")) { KoXmlDocument doc; if (!doc.setContent(resourceStore->device())) { warnKrita << "Could not parse meta.xml for" << filename(); return false; } // First find the manifest:manifest node. KoXmlNode n = doc.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (!n.isElement()) { continue; } if (n.toElement().tagName() == "meta:meta") { break; } } if (n.isNull()) { warnKrita << "Could not find manifest node for bundle" << filename(); return false; } const KoXmlElement metaElement = n.toElement(); for (n = metaElement.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { KoXmlElement e = n.toElement(); if (e.tagName() == "meta:generator") { m_metadata.insert("generator", e.firstChild().toText().data()); } else if (e.tagName() == "dc:author") { m_metadata.insert("author", e.firstChild().toText().data()); } else if (e.tagName() == "dc:title") { m_metadata.insert("title", e.firstChild().toText().data()); } else if (e.tagName() == "dc:description") { m_metadata.insert("description", e.firstChild().toText().data()); } else if (e.tagName() == "meta:initial-creator") { m_metadata.insert("author", e.firstChild().toText().data()); } else if (e.tagName() == "dc:creator") { m_metadata.insert("author", e.firstChild().toText().data()); } else if (e.tagName() == "meta:creation-date") { m_metadata.insert("created", e.firstChild().toText().data()); } else if (e.tagName() == "meta:dc-date") { m_metadata.insert("updated", e.firstChild().toText().data()); } else if (e.tagName() == "meta:meta-userdefined") { if (e.attribute("meta:name") == "tag") { m_bundletags << e.attribute("meta:value"); } else { m_metadata.insert(e.attribute("meta:name"), e.attribute("meta:value")); } } else if(e.tagName() == "meta:bundle-version") { m_metadata.insert("bundle-version", e.firstChild().toText().data()); versionFound = true; } } } resourceStore->close(); } else { warnKrita << "Could not load meta.xml"; return false; } if (resourceStore->open("preview.png")) { // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = resourceStore->device()->readAll(); QBuffer buffer(&data); m_thumbnail.load(&buffer, "PNG"); resourceStore->close(); } else { warnKrita << "Could not open preview.png"; } /* * If no version is found it's an old bundle with md5 hashes to fix, or if some manifest resource entry * doesn't not correspond to a file the bundle is "broken", in both cases we need to recreate the bundle. */ if (!versionFound) { m_metadata.insert("bundle-version", "1"); warnKrita << filename() << " has an old version and possibly wrong resources md5, so it will be recreated."; toRecreate = true; } if (toRecreate) { recreateBundle(resourceStore); } m_installed = true; setValid(true); setImage(m_thumbnail); } return true; } bool KisResourceBundle::loadFromDevice(QIODevice *) { return false; } bool saveResourceToStore(KoResource *resource, KoStore *store, const QString &resType) { if (!resource) { warnKrita << "No Resource"; return false; } if (!resource->valid()) { warnKrita << "Resource is not valid"; return false; } if (!store || store->bad()) { warnKrita << "No Store or Store is Bad"; return false; } QByteArray ba; QBuffer buf; QFileInfo fi(resource->filename()); if (fi.exists() && fi.isReadable()) { QFile f(resource->filename()); if (!f.open(QFile::ReadOnly)) { warnKrita << "Could not open resource" << resource->filename(); return false; } ba = f.readAll(); if (ba.size() == 0) { warnKrita << "Resource is empty" << resource->filename(); return false; } f.close(); buf.setBuffer(&ba); } else { warnKrita << "Could not find the resource " << resource->filename() << " or it isn't readable"; return false; } if (!buf.open(QBuffer::ReadOnly)) { warnKrita << "Could not open buffer"; return false; } Q_ASSERT(!store->hasFile(resType + "/" + resource->shortFilename())); if (!store->open(resType + "/" + resource->shortFilename())) { warnKrita << "Could not open file in store for resource"; return false; } bool res = (store->write(buf.data()) == buf.size()); store->close(); return res; } bool KisResourceBundle::save() { if (filename().isEmpty()) return false; addMeta("updated", QDate::currentDate().toString("dd/MM/yyyy")); QDir bundleDir = KoResourcePaths::saveLocation("data", "bundles"); bundleDir.cdUp(); QScopedPointer store(KoStore::createStore(filename(), KoStore::Write, "application/x-krita-resourcebundle", KoStore::Zip)); if (!store || store->bad()) return false; Q_FOREACH (const QString &resType, m_manifest.types()) { if (resType == "ko_gradients") { KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = gradientServer->resourceByMD5(ref.md5sum); if (!res) res = gradientServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "gradients")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "ko_patterns") { KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = patternServer->resourceByMD5(ref.md5sum); if (!res) res = patternServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "patterns")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "kis_brushes") { KisBrushResourceServer* brushServer = KisBrushServer::instance()->brushServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KisBrushSP brush = brushServer->resourceByMD5(ref.md5sum); if (!brush) brush = brushServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); KoResource *res = brush.data(); if (!saveResourceToStore(res, store.data(), "brushes")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "ko_palettes") { KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = paletteServer->resourceByMD5(ref.md5sum); if (!res) res = paletteServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "palettes")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "kis_workspaces") { KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = workspaceServer->resourceByMD5(ref.md5sum); if (!res) res = workspaceServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "workspaces")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "kis_paintoppresets") { KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(ref.md5sum); if (!res) res = paintoppresetServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res.data(), store.data(), "paintoppresets")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "ko_gamutmasks") { KoResourceServer* gamutMaskServer = KoResourceServerProvider::instance()->gamutMaskServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = gamutMaskServer->resourceByMD5(ref.md5sum); if (!res) res = gamutMaskServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "gamutmasks")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } } if (!m_thumbnail.isNull()) { QByteArray byteArray; QBuffer buffer(&byteArray); m_thumbnail.save(&buffer, "PNG"); if (!store->open("preview.png")) warnKrita << "Could not open preview.png"; if (store->write(byteArray) != buffer.size()) warnKrita << "Could not write preview.png"; store->close(); } saveManifest(store); saveMetadata(store); store->finalize(); return true; } bool KisResourceBundle::saveToDevice(QIODevice */*dev*/) const { return false; } bool KisResourceBundle::install() { QStringList md5Mismatch; if (filename().isEmpty()) { warnKrita << "Cannot install bundle: no file name" << this; return false; } QScopedPointer resourceStore(KoStore::createStore(filename(), KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip)); if (!resourceStore || resourceStore->bad()) { warnKrita << "Cannot open the resource bundle: invalid zip file?"; return false; } Q_FOREACH (const QString &resType, m_manifest.types()) { dbgResources << "Installing resource type" << resType; if (resType == "gradients") { KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoAbstractGradient *res = gradientServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); KoAbstractGradient *res2 = gradientServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... gradientServer->addResource(res, false);//add it! if (!m_gradientsMd5Installed.contains(res->md5())) { m_gradientsMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { gradientServer->addTag(res, tag); } //gradientServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "patterns") { KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoPattern *res = patternServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); KoPattern *res2 = patternServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... patternServer->addResource(res, false);//add it! if (!m_patternsMd5Installed.contains(res->md5())) { m_patternsMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { patternServer->addTag(res, tag); } //patternServer->addTag(res, name()); } } } else if (resType == "brushes") { KisBrushResourceServer *brushServer = KisBrushServer::instance()->brushServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KisBrushSP res = brushServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //find the resource on the server KisBrushSP res2 = brushServer->resourceByName(res->name()); if (res2) { res->setName(res->name()+"("+res->shortFilename()+")"); } // file name is more important than the regular name because the // it is the way how it is called up from the brushpreset settings. // Therefore just adjust the resource name and only refuse to load // when the filename is different. res2 = brushServer->resourceByFilename(res->shortFilename()); if (!res2) {//if it doesn't exist... brushServer->addResource(res, false);//add it! if (!m_brushesMd5Installed.contains(res->md5())) { m_brushesMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { brushServer->addTag(res.data(), tag); } //brushServer->addTag(res.data(), name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "palettes") { KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoColorSet *res = paletteServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //find the resource on the server KoColorSet *res2 = paletteServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... paletteServer->addResource(res, false);//add it! if (!m_palettesMd5Installed.contains(res->md5())) { m_palettesMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { paletteServer->addTag(res, tag); } //paletteServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "workspaces") { KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KisWorkspaceResource *res = workspaceServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //the following tries to find the resource by name. KisWorkspaceResource *res2 = workspaceServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... workspaceServer->addResource(res, false);//add it! if (!m_workspacesMd5Installed.contains(res->md5())) { m_workspacesMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { workspaceServer->addTag(res, tag); } //workspaceServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "paintoppresets") { KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KisPaintOpPresetSP res = paintoppresetServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = resourceStore->device()->readAll(); QBuffer buffer(&data); if (!res->loadFromDevice(&buffer)) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name() << "File:" << res->filename(); //the following tries to find the resource by name. KisPaintOpPresetSP res2 = paintoppresetServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... paintoppresetServer->addResource(res, false);//add it! if (!m_presetsMd5Installed.contains(res->md5())){ m_presetsMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { paintoppresetServer->addTag(res.data(), tag); } //paintoppresetServer->addTag(res.data(), name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "gamutmasks") { KoResourceServer* gamutMaskServer = KoResourceServerProvider::instance()->gamutMaskServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoGamutMask *res = gamutMaskServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //find the resource on the server KoGamutMask *res2 = gamutMaskServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... gamutMaskServer->addResource(res, false);//add it! if (!m_gamutMasksMd5Installed.contains(res->md5())) { m_gamutMasksMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { gamutMaskServer->addTag(res, tag); } //gamutMaskServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } } m_installed = true; if(!md5Mismatch.isEmpty()){ QString message = i18n("The following resources had mismatching MD5 sums. They may have gotten corrupted, for example, during download."); QMessageBox bundleFeedback; bundleFeedback.setIcon(QMessageBox::Warning); Q_FOREACH (QString name, md5Mismatch) { message.append("\n"); message.append(name); } bundleFeedback.setText(message); bundleFeedback.exec(); } return true; } bool KisResourceBundle::uninstall() { m_installed = false; QStringList tags = getTagsList(); tags << m_manifest.tags(); //tags << name(); KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("gradients")) { Q_FOREACH (const QByteArray md5, m_gradientsMd5Installed) { KoAbstractGradient *res = gradientServer->resourceByMD5(md5); if (res) { gradientServer->removeResourceFromServer(res); } } KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("patterns")) { Q_FOREACH (const QByteArray md5, m_patternsMd5Installed) { KoPattern *res = patternServer->resourceByMD5(md5); if (res) { patternServer->removeResourceFromServer(res); } } KisBrushResourceServer *brushServer = KisBrushServer::instance()->brushServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("brushes")) { Q_FOREACH (const QByteArray md5, m_brushesMd5Installed) { KisBrushSP res = brushServer->resourceByMD5(md5); if (res) { brushServer->removeResourceFromServer(res); } } KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("palettes")) { Q_FOREACH (const QByteArray md5, m_palettesMd5Installed) { KoColorSet *res = paletteServer->resourceByMD5(md5); if (res) { paletteServer->removeResourceFromServer(res); } } KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("workspaces")) { Q_FOREACH (const QByteArray md5, m_workspacesMd5Installed) { KisWorkspaceResource *res = workspaceServer->resourceByMD5(md5); if (res) { workspaceServer->removeResourceFromServer(res); } } KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("paintoppresets")) { Q_FOREACH (const QByteArray md5, m_presetsMd5Installed) { KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(md5); if (res) { paintoppresetServer->removeResourceFromServer(res); } } KoResourceServer* gamutMaskServer = KoResourceServerProvider::instance()->gamutMaskServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("gamutmasks")) { Q_FOREACH (const QByteArray md5, m_gamutMasksMd5Installed) { KoGamutMask *res = gamutMaskServer->resourceByMD5(md5); if (res) { gamutMaskServer->removeResourceFromServer(res); } } Q_FOREACH(const QString &tag, tags) { paintoppresetServer->tagCategoryRemoved(tag); workspaceServer->tagCategoryRemoved(tag); paletteServer->tagCategoryRemoved(tag); brushServer->tagCategoryRemoved(tag); patternServer->tagCategoryRemoved(tag); gradientServer->tagCategoryRemoved(tag); gamutMaskServer->tagCategoryRemoved(tag); } return true; } void KisResourceBundle::addMeta(const QString &type, const QString &value) { m_metadata.insert(type, value); } const QString KisResourceBundle::getMeta(const QString &type, const QString &defaultValue) const { if (m_metadata.contains(type)) { return m_metadata[type]; } else { return defaultValue; } } void KisResourceBundle::addResource(QString fileType, QString filePath, QStringList fileTagList, const QByteArray md5sum) { m_manifest.addResource(fileType, filePath, fileTagList, md5sum); } QList KisResourceBundle::getTagsList() { return QList::fromSet(m_bundletags); } bool KisResourceBundle::isInstalled() { return m_installed; } QStringList KisResourceBundle::resourceTypes() const { return m_manifest.types(); } QList KisResourceBundle::resources(const QString &resType) const { QList references = m_manifest.files(resType); QList ret; Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, references) { if (resType == "gradients") { KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); KoResource *res = gradientServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "patterns") { KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); KoResource *res = patternServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "brushes") { KisBrushResourceServer *brushServer = KisBrushServer::instance()->brushServer(); KoResource *res = brushServer->resourceByMD5(ref.md5sum).data(); if (res) ret << res; } else if (resType == "palettes") { KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); KoResource *res = paletteServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "workspaces") { KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); KoResource *res = workspaceServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "paintoppresets") { KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(ref.md5sum); if (res) ret << res.data(); } else if (resType == "gamutmasks") { KoResourceServer* gamutMaskServer = KoResourceServerProvider::instance()->gamutMaskServer(); KoResource *res = gamutMaskServer->resourceByMD5(ref.md5sum); if (res) ret << res; } } return ret; } void KisResourceBundle::setThumbnail(QString filename) { if (QFileInfo(filename).exists()) { m_thumbnail = QImage(filename); m_thumbnail = m_thumbnail.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { m_thumbnail = QImage(256, 256, QImage::Format_ARGB32); QPainter gc(&m_thumbnail); gc.fillRect(0, 0, 256, 256, Qt::red); gc.end(); } setImage(m_thumbnail); } void KisResourceBundle::writeMeta(const char *metaTag, const QString &metaKey, KoXmlWriter *writer) { if (m_metadata.contains(metaKey)) { writer->startElement(metaTag); writer->addTextNode(m_metadata[metaKey].toUtf8()); writer->endElement(); } } void KisResourceBundle::writeUserDefinedMeta(const QString &metaKey, KoXmlWriter *writer) { if (m_metadata.contains(metaKey)) { writer->startElement("meta:meta-userdefined"); writer->addAttribute("meta:name", metaKey); writer->addAttribute("meta:value", m_metadata[metaKey]); writer->endElement(); } } void KisResourceBundle::setInstalled(bool install) { m_installed = install; } void KisResourceBundle::saveMetadata(QScopedPointer &store) { QBuffer buf; store->open("meta.xml"); buf.open(QBuffer::WriteOnly); KoXmlWriter metaWriter(&buf); metaWriter.startDocument("office:document-meta"); metaWriter.startElement("meta:meta"); writeMeta("meta:generator", "generator", &metaWriter); metaWriter.startElement("meta:bundle-version"); metaWriter.addTextNode(m_bundleVersion.toUtf8()); metaWriter.endElement(); writeMeta("dc:author", "author", &metaWriter); writeMeta("dc:title", "filename", &metaWriter); writeMeta("dc:description", "description", &metaWriter); writeMeta("meta:initial-creator", "author", &metaWriter); writeMeta("dc:creator", "author", &metaWriter); writeMeta("meta:creation-date", "created", &metaWriter); writeMeta("meta:dc-date", "updated", &metaWriter); writeUserDefinedMeta("email", &metaWriter); writeUserDefinedMeta("license", &metaWriter); writeUserDefinedMeta("website", &metaWriter); Q_FOREACH (const QString &tag, m_bundletags) { metaWriter.startElement("meta:meta-userdefined"); metaWriter.addAttribute("meta:name", "tag"); metaWriter.addAttribute("meta:value", tag); metaWriter.endElement(); } metaWriter.endElement(); // meta:meta metaWriter.endDocument(); buf.close(); store->write(buf.data()); store->close(); } void KisResourceBundle::saveManifest(QScopedPointer &store) { store->open("META-INF/manifest.xml"); QBuffer buf; buf.open(QBuffer::WriteOnly); m_manifest.save(&buf); buf.close(); store->write(buf.data()); store->close(); } void KisResourceBundle::recreateBundle(QScopedPointer &oldStore) { // Save a copy of the unmodified bundle, so that if anything goes bad the user doesn't lose it QFile file(filename()); file.copy(filename() + ".old"); QString newStoreName = filename() + ".tmp"; { QScopedPointer store(KoStore::createStore(newStoreName, KoStore::Write, "application/x-krita-resourcebundle", KoStore::Zip)); KoHashGenerator *generator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); KisResourceBundleManifest newManifest; addMeta("updated", QDate::currentDate().toString("dd/MM/yyyy")); Q_FOREACH (KisResourceBundleManifest::ResourceReference ref, m_manifest.files()) { // Wrong manifest entry found, skip it if(!oldStore->open(ref.resourcePath)) continue; store->open(ref.resourcePath); QByteArray data = oldStore->device()->readAll(); oldStore->close(); store->write(data); store->close(); QByteArray result = generator->generateHash(data); newManifest.addResource(ref.fileTypeName, ref.resourcePath, ref.tagList, result); } m_manifest = newManifest; if (!m_thumbnail.isNull()) { QByteArray byteArray; QBuffer buffer(&byteArray); m_thumbnail.save(&buffer, "PNG"); if (!store->open("preview.png")) warnKrita << "Could not open preview.png"; if (store->write(byteArray) != buffer.size()) warnKrita << "Could not write preview.png"; store->close(); } saveManifest(store); saveMetadata(store); store->finalize(); } // Remove the current bundle and then move the tmp one to be the correct one file.setFileName(filename()); if (!file.remove()) { qWarning() << "Could not remove" << filename() << file.errorString(); } QFile f(newStoreName); Q_ASSERT(f.exists()); if (!f.copy(filename())) { qWarning() << "Could not copy the tmp file to the store" << filename() << newStoreName << QFile(newStoreName).exists() << f.errorString(); } } int KisResourceBundle::resourceCount() const { return m_manifest.files().count(); } diff --git a/libs/ui/KisTemplateTree.cpp b/libs/ui/KisTemplateTree.cpp index 346de5b444..a6312d8d25 100644 --- a/libs/ui/KisTemplateTree.cpp +++ b/libs/ui/KisTemplateTree.cpp @@ -1,289 +1,289 @@ /* This file is part of the KDE project Copyright (C) 2000 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisTemplateTree.h" #include #include #include #include #include #include #include #include #include #include #include #include KisTemplateTree::KisTemplateTree(const QString &templatesResourcePath, bool readTree) : m_templatesResourcePath(templatesResourcePath) , m_defaultGroup(0) , m_defaultTemplate(0) { if (readTree) readTemplateTree(); } KisTemplateTree::~KisTemplateTree() { qDeleteAll(m_groups); } void KisTemplateTree::readTemplateTree() { readGroups(); readTemplates(); } void KisTemplateTree::writeTemplateTree() { QString localDir = KoResourcePaths::saveLocation("templates"); Q_FOREACH (KisTemplateGroup *group, m_groups) { //dbgUI <<"---------------------------------"; //dbgUI <<"group:" << group->name(); bool touched = false; QList templates = group->templates(); QList::iterator it = templates.begin(); for (; it != templates.end() && !touched && !group->touched(); ++it) touched = (*it)->touched(); if (group->touched() || touched) { //dbgUI <<"touched"; if (!group->isHidden()) { //dbgUI <<"not hidden"; QDir path; path.mkpath(localDir + group->name()); // create the local group dir } else { //dbgUI <<"hidden"; if (group->dirs().count() == 1 && group->dirs().contains(localDir)) { //dbgUI <<"local only"; QFile f(group->dirs().first()); f.remove(); //dbgUI <<"removing:" << group->dirs().first(); } else { //dbgUI <<"global"; QDir path; path.mkpath(localDir + group->name()); } } } Q_FOREACH (KisTemplate *t, templates) { if (t->touched()) { //dbgUI <<"++template:" << t->name(); writeTemplate(t, group, localDir); } if (t->isHidden() && t->touched()) { //dbgUI <<"+++ delete local template ##############"; writeTemplate(t, group, localDir); QFile::remove(t->file()); QFile::remove(t->picture()); } } } } void KisTemplateTree::add(KisTemplateGroup *g) { KisTemplateGroup *group = find(g->name()); if (group == 0) m_groups.append(g); else { group->addDir(g->dirs().first()); // "...there can be only one..." (Queen) delete g; g = 0; } } KisTemplateGroup *KisTemplateTree::find(const QString &name) const { QList::const_iterator it = m_groups.begin(); KisTemplateGroup* ret = 0; while (it != m_groups.end()) { if ((*it)->name() == name) { ret = *it; break; } ++it; } return ret; } void KisTemplateTree::readGroups() { QStringList dirs = KoResourcePaths::findDirs("templates"); Q_FOREACH (const QString & dirName, dirs) { if (!dirName.contains("templates")) continue; // Hack around broken KoResourcePaths QDir dir(dirName); // avoid the annoying warning if (!dir.exists()) continue; QStringList templateDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); Q_FOREACH (const QString & templateDirName, templateDirs) { QDir templateDir(dirName + "/" + templateDirName); QString name = templateDirName; QString defaultTab; int sortingWeight = 1000; if (templateDir.exists(".directory")) { KDesktopFile config(templateDir.absoluteFilePath(".directory")); KConfigGroup dg = config.desktopGroup(); name = dg.readEntry("Name"); defaultTab = dg.readEntry("X-KDE-DefaultTab"); sortingWeight = dg.readEntry("X-KDE-SortingWeight", 1000); } KisTemplateGroup *g = new KisTemplateGroup(name, templateDir.absolutePath() + QDir::separator(), sortingWeight); add(g); if (defaultTab == "true") m_defaultGroup = g; } } } void KisTemplateTree::readTemplates() { QString dontShow = "imperial"; if ( QLocale().measurementSystem() == QLocale::ImperialSystem) { dontShow = "metric"; } Q_FOREACH (KisTemplateGroup* group, m_groups) { QStringList dirs = group->dirs(); for (QStringList::ConstIterator it = dirs.constBegin(); it != dirs.constEnd(); ++it) { QDir d(*it); if (!d.exists()) continue; QStringList files = d.entryList(QDir::Files | QDir::Readable, QDir::Name); for (int i = 0; i < files.count(); ++i) { QString filePath = *it + files[i]; //dbgUI <<"filePath:" << filePath; QString icon; QString text; QString description; QString hidden_str; QString fileName; bool hidden = false; bool defaultTemplate = false; QString templatePath; QString measureSystem; // If a desktop file, then read the name from it. // Otherwise (or if no name in it?) use file name if (KDesktopFile::isDesktopFile(filePath)) { KConfig _config(filePath, KConfig::SimpleConfig); KConfigGroup config(&_config, "Desktop Entry"); if (config.readEntry("Type") == "Link") { text = config.readEntry("Name"); fileName = filePath; description = config.readEntry("Comment"); //dbgUI <<"name:" << text; icon = config.readEntry("Icon"); if (icon[0] != '/' && // allow absolute paths for icons QFile::exists(*it + icon)) // allow icons from icontheme icon = *it + icon; //dbgUI <<"icon2:" << icon; hidden = config.readEntry("X-KDE-Hidden", false); defaultTemplate = config.readEntry("X-KDE-DefaultTemplate", false); measureSystem = config.readEntry("X-KDE-MeasureSystem").toLower(); // Don't add a template that is for the wrong measure system if (measureSystem == dontShow) continue; //dbgUI <<"hidden:" << hidden_str; templatePath = config.readPathEntry("URL", QString()); //dbgUI <<"Link to :" << templatePath; if (templatePath[0] != '/') { if (templatePath.left(6) == "file:/") // I doubt this will happen templatePath = templatePath.right(templatePath.length() - 6); //else // dbgUI <<"dirname=" << *it; templatePath = *it + templatePath; //dbgUI <<"templatePath:" << templatePath; } } else continue; // Invalid } // The else if and the else branch are here for compat. with the old system else if (files[i].right(4) != ".png") // Ignore everything that is not a PNG file continue; else { // Found a PNG file - the template must be here in the same dir. icon = filePath; QFileInfo fi(filePath); - text = fi.baseName(); + text = fi.completeBaseName(); templatePath = filePath; // Note that we store the .png file as the template ! // That's the way it's always been done. Then the app replaces the extension... } KisTemplate *t = new KisTemplate(text, description, templatePath, icon, fileName, measureSystem, hidden); group->add(t, false, false); // false -> we aren't a "user", false -> don't // "touch" the group to avoid useless // creation of dirs in .kde/blah/... if (defaultTemplate) m_defaultTemplate = t; } } } } void KisTemplateTree::writeTemplate(KisTemplate *t, KisTemplateGroup *group, const QString &localDir) { QString fileName; if (t->isHidden()) { fileName = t->fileName(); // try to remove the file if (QFile::remove(fileName) || !QFile::exists(fileName)) { QFile::remove(t->name()); QFile::remove(t->picture()); return; } } // be sure that the template's file name is unique so we don't overwrite an other QString const path = localDir + group->name() + '/'; QString const name = KisTemplates::trimmed(t->name()); fileName = path + name + ".desktop"; if (t->isHidden() && QFile::exists(fileName)) return; QString fill; while (QFile(fileName).exists()) { fill += '_'; fileName = path + fill + name + ".desktop"; } KConfig _config(fileName, KConfig::SimpleConfig); KConfigGroup config(&_config, "Desktop Entry"); config.writeEntry("Type", "Link"); config.writePathEntry("URL", t->file()); config.writeEntry("Name", t->name()); config.writeEntry("Icon", t->picture()); config.writeEntry("X-KDE-Hidden", t->isHidden()); } diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 8ce74f93e3..5f90fa5364 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,620 +1,620 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "kis_shape_layer.h" #include #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_update_outline_job.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false, const KisTimeRange &range = KisTimeRange()) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft(), range); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->clear(); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectActiveSelectionCommand(view->selection(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectActiveSelectionCommand(view->activeNode(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->canvasResourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); view->canvasResourceProvider()->slotPainting(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } KisTimeRange range; KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (channel) { const int currentTime = image->animationInterface()->currentTime(); range = channel->affectedFrames(currentTime); } ActionHelper::copyFromDevice(view, dev, makeSharpClip, range); } KUndo2Command *command = 0; if (willCut && node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a vector format "), QIcon(), 2000, KisFloatingMessage::Low); return; } if (!selection->outlineCacheValid()) { view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent)); if (!view->blockUntilOperationsFinished(view->image())) { return; } } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisSelectionToRasterActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a raster format "), QIcon(), 2000, KisFloatingMessage::Low); return; } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); struct RasterizeSelection : public KisTransactionBasedCommand { RasterizeSelection(KisSelectionSP sel) : m_sel(sel) {} KisSelectionSP m_sel; KUndo2Command* paint() override { // just create an empty transaction: it will rasterize the // selection and emit the necessary signals KisTransaction transaction(m_sel->pixelSelection()); return transaction.endAndTake(); } }; ap->applyCommand(new RasterizeSelection(selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); bool hasSelectionShapes = false; QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { if (dynamic_cast(shape->userData())) { hasSelectionShapes = true; continue; } clonedShapes << shape->cloneShape(); } if (clonedShapes.isEmpty()) { if (hasSelectionShapes) { view->showFloatingMessage(i18nc("floating message", "The shape already belongs to a selection"), QIcon(), 2000, KisFloatingMessage::Low); } return; } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor())); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisPainter::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else if (currentNode->inherits("KisShapeLayer")) { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStrokeSP border(new KoShapeStroke(size, color)); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } image->setModified(); } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, - rManager , + rManager, strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index f246978d85..e162943bd3 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1293 +1,1297 @@ /* 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 #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 "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_selection_mask.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" #include "kis_algebra_2d.h" #include "kis_image_signal_router.h" #include "KisSnapPixelStrategy.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceProvider* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor canvasUpdateCompressor; 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 lodAllowedInImage = false; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup; KisSignalCompressor frameRenderStartCompressor; KisSignalCompressor regionOfInterestUpdateCompressor; QRect regionOfInterest; QRect renderingLimit; int isBatchUpdateActive = 0; bool effectiveLodAllowedInImage() { return lodAllowedInImage && !bootstrapLodBlocked; } void setActiveShapeManager(KoShapeManager *shapeManager); }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisSelectionSP selection; if (KisLayer *layer = dynamic_cast(node.data())) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } } else if (KisSelectionMask *mask = dynamic_cast(node.data())) { selection = mask->selection(); } if (!shapeManager && 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, KoCanvasResourceProvider *resourceManager, KisView *view, KoShapeControllerBase *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())); connect(view->mainWindow(), SIGNAL(screenChanged()), SLOT(slotConfigChanged())); KisImageConfig config(false); m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit()); m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit()); m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); snapGuide()->overrideSnapStrategy(KoSnapGuide::PixelSnapping, new KisSnapPixelStrategy()); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInImage = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInImage); 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()), selectedShapesProxy(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start())); connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection())); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest())); connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg(true); 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(KisAbstractCanvasWidget *widget) { if (m_d->popupPalette) { m_d->popupPalette->setParent(widget->widget()); } if (m_d->canvasWidget != 0) { widget->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = widget; // 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->widget()->setAutoFillBackground(false); widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent); widget->widget()->setMouseTracking(true); widget->widget()->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller && controller->canvas() == this) { controller->changeCanvasWidget(widget->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); } void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager) { if (shapeManager != currentlyActiveShapeManager) { currentlyActiveShapeManager = shapeManager; selectedShapesProxy.setShapeManager(shapeManager); } } KoShapeManager* KisCanvas2::shapeManager() const { KoShapeManager *localShapeManager = this->localShapeManager(); // 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; } KoShapeManager *KisCanvas2::localShapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); if (localShapeManager != m_d->currentlyActiveShapeManager) { m_d->setActiveShapeManager(localShapeManager); } return localShapeManager; } 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(); } KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const { return m_d->inputActionGroupsMask; } void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask) { m_d->inputActionGroupsMask = mask; } 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(true); 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(true); QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL()); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL && !KisOpenGL::hasOpenGL()) { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; useOpenGL = false; } m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL); if (useOpenGL) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false); 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->displayColorConverter.setImageColorSpace(image->colorSpace()); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged())); connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged())); 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(); setLodAllowedInCanvas(m_d->lodAllowedInImage); emit sigCanvasEngineChanged(); } 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(true); 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) { /** * We don't do patched loading for openGL canvas, becasue it loads * the tiles, which are bascially "patches". Therefore, big chunks * of memory are never allocated. */ if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig(true); 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(); } void KisCanvas2::slotImageColorSpaceChanged() { KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); image->barrierLock(); m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace()); image->unlock(); } 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) { KisImageConfig cfg(false); m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; #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 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(true).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() { auto tryIssueCanvasUpdates = [this](const QRect &vRect) { if (!m_d->isBatchUpdateActive) { // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { m_d->savedUpdateRect = QRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) { m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } } }; auto uploadData = [this, tryIssueCanvasUpdates](const QVector &infoObjects) { QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects); const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(), QRect(), std::bit_or()); tryIssueCanvasUpdates(vRect); }; bool shouldExplicitlyIssueUpdates = false; QVector infoObjects; KisUpdateInfoList originalInfoObjects; m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects); for (auto it = originalInfoObjects.constBegin(); it != originalInfoObjects.constEnd(); ++it) { KisUpdateInfoSP info = *it; const KisMarkerUpdateInfo *batchInfo = dynamic_cast(info.data()); if (batchInfo) { if (!infoObjects.isEmpty()) { uploadData(infoObjects); infoObjects.clear(); } if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) { m_d->isBatchUpdateActive++; } else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) { m_d->isBatchUpdateActive--; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0); if (m_d->isBatchUpdateActive == 0) { shouldExplicitlyIssueUpdates = true; } } else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(true); } else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(false); shouldExplicitlyIssueUpdates = true; } } else { infoObjects << info; } } if (!infoObjects.isEmpty()) { uploadData(infoObjects); } else if (shouldExplicitlyIssueUpdates) { tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels()); } } void KisCanvas2::slotBeginUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotEndUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotSetLodUpdatesBlocked(bool value) { KisUpdateInfoSP info = new KisMarkerUpdateInfo(value ? KisMarkerUpdateInfo::BlockLodUpdates : KisMarkerUpdateInfo::UnblockLodUpdates, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotDoCanvasUpdate() { /** * WARNING: in isBusy() we access openGL functions without making the painting * context current. We hope that currently active context will be Qt's one, * which is shared with our own. */ 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->canvasUpdateCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->canvasUpdateCompressor.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 m_d->regionOfInterestUpdateCompressor.start(); } QRect KisCanvas2::regionOfInterest() const { return m_d->regionOfInterest; } void KisCanvas2::slotUpdateRegionOfInterest() { const QRect oldRegionOfInterest = m_d->regionOfInterest; const qreal ratio = 0.25; const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect(); const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels(); m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect; if (m_d->regionOfInterest != oldRegionOfInterest) { emit sigRegionOfInterestChanged(m_d->regionOfInterest); } } void KisCanvas2::slotReferenceImagesChanged() { canvasController()->resetScrollBars(); } void KisCanvas2::setRenderingLimit(const QRect &rc) { m_d->renderingLimit = rc; } QRect KisCanvas2::renderingLimit() const { return m_d->renderingLimit; } void KisCanvas2::slotTrySwitchShapeManager() { KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); m_d->setActiveShapeManager(newManager); } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInImage()) return; const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg(true); const int maxLod = cfg.numMipmapLevels(); const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); if (m_d->effectiveLodAllowedInImage()) { KisImageSP image = this->image(); 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(); // The given offset is in widget logical pixels. In order to prevent fuzzy // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio // is not integral, we adjusts the offset to map to whole device pixels. // // FIXME: This is a temporary hack for fixing the canvas under fractional // DPI scaling before a new coordinate system is introduced. QPointF offsetAdjusted = m_d->coordinatesConverter->snapToDevicePixel(documentOffset); m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); m_d->regionOfInterestUpdateCompressor.start(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); // HACK: Sometimes screenNumber(this->canvasWidget()) is not able to get the // proper screenNumber when moving the window across screens. Using // the coordinates should be able to work around this. // FIXME: We should change to associate the display profiles with the screen // model and serial number instead. See https://bugs.kde.org/show_bug.cgi?id=407498 QScreen *canvasScreen = this->canvasWidget()->window()->windowHandle()->screen(); QPoint canvasScreenCenter = canvasScreen->geometry().center(); int canvasScreenNumber = QApplication::desktop()->screenNumber(canvasScreenCenter); if (canvasScreenNumber == -1) { // Fall back to the old way of getting the screenNumber canvasScreenNumber = QApplication::desktop()->screenNumber(this->canvasWidget()); } if (canvasScreenNumber != -1) { setDisplayProfile(cfg.displayProfile(canvasScreenNumber)); } else { warnUI << "Failed to get screenNumber for updating display profile."; } initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::setDisplayProfile(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->setDisplayColorConverter(&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(slotPopupPaletteRequestedZoomChange(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotPopupPaletteRequestedZoomChange(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->lodAllowedInImage); } 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->lodAllowedInImage = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage()); } notifyLevelOfDetailChange(); KisConfig cfg(false); cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage); + + KisUsageLogger::log(QString("Instant Preview Setting: %1").arg(m_d->lodAllowedInImage)); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInImage; } 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()); } KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const { KisCanvasDecorationSP deco = decoration("referenceImagesDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/canvas/kis_tool_proxy.cpp b/libs/ui/canvas/kis_tool_proxy.cpp index 5c054df964..5e98626a89 100644 --- a/libs/ui/canvas/kis_tool_proxy.cpp +++ b/libs/ui/canvas/kis_tool_proxy.cpp @@ -1,243 +1,259 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "input/kis_tablet_debugger.h" #include KisToolProxy::KisToolProxy(KoCanvasBase *canvas, QObject *parent) : KoToolProxy(canvas, parent), m_isActionActivated(false), m_lastAction(KisTool::Primary) { } void KisToolProxy::initializeImage(KisImageSP image) { connect(image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image, SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image, SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); } QPointF KisToolProxy::tabletToDocument(const QPointF &globalPos) { const QPointF pos = globalPos - QPointF(canvas()->canvasWidget()->mapToGlobal(QPoint(0, 0))); return widgetToDocument(pos); } QPointF KisToolProxy::widgetToDocument(const QPointF &widgetPoint) const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->widgetToDocument(widgetPoint); } KoPointerEvent KisToolProxy::convertEventToPointerEvent(QEvent *event, const QPointF &docPoint, bool *result) { switch (event->type()) { case QEvent::TabletPress: case QEvent::TabletRelease: case QEvent::TabletMove: { *result = true; QTabletEvent *tabletEvent = static_cast(event); KoPointerEvent ev(tabletEvent, docPoint); ev.setTabletButton(Qt::LeftButton); return ev; } case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: case QEvent::MouseMove: { *result = true; QMouseEvent *mouseEvent = static_cast(event); return KoPointerEvent(mouseEvent, docPoint); } + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + *result = true; + QTouchEvent *touchEvent = static_cast (event); + return KoPointerEvent(touchEvent, docPoint); + } default: ; } *result = false; QMouseEvent fakeEvent(QEvent::MouseMove, QPoint(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); return KoPointerEvent(&fakeEvent, QPointF()); } void KisToolProxy::forwardHoverEvent(QEvent *event) { switch (event->type()) { case QEvent::TabletMove: { QTabletEvent *tabletEvent = static_cast(event); QPointF docPoint = widgetToDocument(tabletEvent->posF()); this->tabletEvent(tabletEvent, docPoint); return; } case QEvent::MouseMove: { QMouseEvent *mouseEvent = static_cast(event); QPointF docPoint = widgetToDocument(mouseEvent->localPos()); mouseMoveEvent(mouseEvent, docPoint); return; } default: { qWarning() << "forwardHoverEvent encountered unknown event type."; return; } } } bool KisToolProxy::forwardEvent(ActionState state, KisTool::ToolAction action, QEvent *event, QEvent *originalEvent) { bool retval = true; QTabletEvent *tabletEvent = dynamic_cast(event); QMouseEvent *mouseEvent = dynamic_cast(event); + QTouchEvent *touchEvent = dynamic_cast (event); if (tabletEvent) { QPointF docPoint = widgetToDocument(tabletEvent->posF()); tabletEvent->accept(); this->tabletEvent(tabletEvent, docPoint); forwardToTool(state, action, tabletEvent, docPoint); retval = tabletEvent->isAccepted(); } else if (mouseEvent) { QPointF docPoint = widgetToDocument(mouseEvent->localPos()); mouseEvent->accept(); if (mouseEvent->type() == QEvent::MouseButtonPress) { mousePressEvent(mouseEvent, docPoint); } else if (mouseEvent->type() == QEvent::MouseButtonDblClick) { mouseDoubleClickEvent(mouseEvent, docPoint); } else if (mouseEvent->type() == QEvent::MouseButtonRelease) { mouseReleaseEvent(mouseEvent, docPoint); } else if (mouseEvent->type() == QEvent::MouseMove) { mouseMoveEvent(mouseEvent, docPoint); } forwardToTool(state, action, originalEvent, docPoint); retval = mouseEvent->isAccepted(); } + else if (touchEvent) { + QPointF docPoint = widgetToDocument(touchEvent->touchPoints().at(0).pos()); + touchEvent->accept(); + this->touchEvent(touchEvent, docPoint); + forwardToTool(state, action, touchEvent, docPoint); + retval = touchEvent->isAccepted(); + } else if (event && event->type() == QEvent::KeyPress) { QKeyEvent* kevent = static_cast(event); keyPressEvent(kevent); } else if (event && event->type() == QEvent::KeyRelease) { QKeyEvent* kevent = static_cast(event); keyReleaseEvent(kevent); } return retval; } void KisToolProxy::forwardToTool(ActionState state, KisTool::ToolAction action, QEvent *event, const QPointF &docPoint) { bool eventValid = false; KoPointerEvent ev = convertEventToPointerEvent(event, docPoint, &eventValid); KisTool *activeTool = dynamic_cast(priv()->activeTool); if (!eventValid || !activeTool) return; switch (state) { case BEGIN: if (action == KisTool::Primary) { if (event->type() == QEvent::MouseButtonDblClick) { activeTool->beginPrimaryDoubleClickAction(&ev); } else { activeTool->beginPrimaryAction(&ev); } } else { if (event->type() == QEvent::MouseButtonDblClick) { activeTool->beginAlternateDoubleClickAction(&ev, KisTool::actionToAlternateAction(action)); } else { activeTool->beginAlternateAction(&ev, KisTool::actionToAlternateAction(action)); } } break; case CONTINUE: if (action == KisTool::Primary) { activeTool->continuePrimaryAction(&ev); } else { activeTool->continueAlternateAction(&ev, KisTool::actionToAlternateAction(action)); } break; case END: if (action == KisTool::Primary) { activeTool->endPrimaryAction(&ev); } else { activeTool->endAlternateAction(&ev, KisTool::actionToAlternateAction(action)); } break; } } bool KisToolProxy::primaryActionSupportsHiResEvents() const { KisTool *activeTool = dynamic_cast(const_cast(this)->priv()->activeTool); return activeTool && activeTool->primaryActionSupportsHiResEvents(); } void KisToolProxy::setActiveTool(KoToolBase *tool) { if (!tool) return; if (m_isActionActivated) { deactivateToolAction(m_lastAction); KoToolProxy::setActiveTool(tool); activateToolAction(m_lastAction); } else { KoToolProxy::setActiveTool(tool); } } void KisToolProxy::activateToolAction(KisTool::ToolAction action) { KisTool *activeTool = dynamic_cast(const_cast(this)->priv()->activeTool); if (activeTool) { if (action == KisTool::Primary) { activeTool->activatePrimaryAction(); } else { activeTool->activateAlternateAction(KisTool::actionToAlternateAction(action)); } } m_isActionActivated = true; m_lastAction = action; } void KisToolProxy::deactivateToolAction(KisTool::ToolAction action) { KisTool *activeTool = dynamic_cast(const_cast(this)->priv()->activeTool); if (activeTool) { if (action == KisTool::Primary) { activeTool->deactivatePrimaryAction(); } else { activeTool->deactivateAlternateAction(KisTool::actionToAlternateAction(action)); } } m_isActionActivated = false; m_lastAction = action; } diff --git a/libs/ui/dialogs/KisSessionManagerDialog.cpp b/libs/ui/dialogs/KisSessionManagerDialog.cpp index 6382254a2a..0412c01417 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.cpp +++ b/libs/ui/dialogs/KisSessionManagerDialog.cpp @@ -1,150 +1,151 @@ /* * Copyright (c) 2018 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 #include #include #include #include #include "KisSessionManagerDialog.h" KisSessionManagerDialog::KisSessionManagerDialog(QWidget *parent) : QDialog(parent) { setupUi(this); updateSessionList(); connect(btnNew, SIGNAL(clicked()), this, SLOT(slotNewSession())); connect(btnRename, SIGNAL(clicked()), this, SLOT(slotRenameSession())); connect(btnSwitchTo, SIGNAL(clicked()), this, SLOT(slotSwitchSession())); connect(btnDelete, SIGNAL(clicked()), this, SLOT(slotDeleteSession())); connect(btnClose, SIGNAL(clicked()), this, SLOT(slotClose())); connect(lstSessions, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotSessionDoubleClicked(QListWidgetItem*))); } void KisSessionManagerDialog::updateSessionList() { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); lstSessions->clear(); Q_FOREACH(KisSessionResource *session, server->resources()) { lstSessions->addItem(session->name()); } } void KisSessionManagerDialog::slotNewSession() { QString name = QInputDialog::getText(this, i18n("Create session"), i18n("Session name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; auto *session = new KisSessionResource(QString()); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); QString saveLocation = server->saveLocation(); QFileInfo fileInfo(saveLocation + name + session->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + session->defaultFileExtension()); i++; } session->setFilename(fileInfo.filePath()); session->setName(name); session->storeCurrentWindows(); server->addResource(session); KisPart::instance()->setCurrentSession(session); updateSessionList(); } void KisSessionManagerDialog::slotRenameSession() { QString name = QInputDialog::getText(this, i18n("Rename session"), i18n("New name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; KisSessionResource *session = getSelectedSession(); if (!session) return; session->setName(name); session->save(); updateSessionList(); } void KisSessionManagerDialog::slotSessionDoubleClicked(QListWidgetItem* /*item*/) { slotSwitchSession(); slotClose(); } void KisSessionManagerDialog::slotSwitchSession() { KisSessionResource *session = getSelectedSession(); if (session) { bool closed = KisPart::instance()->closeSession(true); if (closed) { session->restore(); } } } KisSessionResource *KisSessionManagerDialog::getSelectedSession() const { QListWidgetItem *item = lstSessions->currentItem(); if (item) { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); return server->resourceByName(item->text()); } return nullptr; } void KisSessionManagerDialog::slotDeleteSession() { KisSessionResource *session = getSelectedSession(); if (!session) return; if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), QString(i18n("Permanently delete session %1?", session->name())), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + KisPart::instance()->setCurrentSession(0); const QString filename = session->filename(); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); server->removeResourceFromServer(session); QFile file(filename); file.remove(); updateSessionList(); } } void KisSessionManagerDialog::slotClose() { hide(); } diff --git a/libs/ui/dialogs/kis_dlg_layer_style.cpp b/libs/ui/dialogs/kis_dlg_layer_style.cpp index f632b94176..d8d7eaabd6 100644 --- a/libs/ui/dialogs/kis_dlg_layer_style.cpp +++ b/libs/ui/dialogs/kis_dlg_layer_style.cpp @@ -1,1385 +1,1385 @@ /* * Copyright (c) 2014 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_layer_style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cmb_contour.h" #include "kis_cmb_gradient.h" #include "KisResourceServerProvider.h" #include "kis_psd_layer_style_resource.h" #include "kis_psd_layer_style.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_canvas_resource_provider.h" #include KoAbstractGradient* fetchGradientLazy(KoAbstractGradient *gradient, KisCanvasResourceProvider *resourceProvider) { if (!gradient) { gradient = resourceProvider->currentGradient(); } return gradient; } KisDlgLayerStyle::KisDlgLayerStyle(KisPSDLayerStyleSP layerStyle, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : KoDialog(parent) , m_layerStyle(layerStyle) , m_initialLayerStyle(layerStyle->clone()) , m_isSwitchingPredefinedStyle(false) , m_sanityLayerStyleDirty(false) { setCaption(i18n("Layer Styles")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_configChangedCompressor = new KisSignalCompressor(1000, KisSignalCompressor::POSTPONE, this); connect(m_configChangedCompressor, SIGNAL(timeout()), SIGNAL(configChanged())); QWidget *page = new QWidget(this); wdgLayerStyles.setupUi(page); setMainWidget(page); wdgLayerStyles.chkPreview->setVisible(false); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(notifyGuiConfigChanged())); m_stylesSelector = new StylesSelector(this); connect(m_stylesSelector, SIGNAL(styleSelected(KisPSDLayerStyleSP)), SLOT(notifyPredefinedStyleSelected(KisPSDLayerStyleSP))); wdgLayerStyles.stylesStack->addWidget(m_stylesSelector); m_blendingOptions = new BlendingOptions(this); wdgLayerStyles.stylesStack->addWidget(m_blendingOptions); m_dropShadow = new DropShadow(DropShadow::DropShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_dropShadow); connect(m_dropShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerShadow = new DropShadow(DropShadow::InnerShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_innerShadow); connect(m_innerShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_outerGlow = new InnerGlow(InnerGlow::OuterGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_outerGlow); connect(m_outerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerGlow = new InnerGlow(InnerGlow::InnerGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_innerGlow); connect(m_innerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); // Contour and Texture are sub-styles of Bevel and Emboss // They are only applied to canvas when Bevel and Emboss is active. m_contour = new Contour(this); m_texture = new Texture(this); m_bevelAndEmboss = new BevelAndEmboss(m_contour, m_texture, this); wdgLayerStyles.stylesStack->addWidget(m_bevelAndEmboss); wdgLayerStyles.stylesStack->addWidget(m_contour); wdgLayerStyles.stylesStack->addWidget(m_texture); // slotBevelAndEmbossChanged(QListWidgetItem*) enables/disables Contour and Texture on "Bevel and Emboss" toggle. connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(slotBevelAndEmbossChanged(QListWidgetItem*))); connect(m_bevelAndEmboss, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_satin = new Satin(this); wdgLayerStyles.stylesStack->addWidget(m_satin); connect(m_satin, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_colorOverlay = new ColorOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_colorOverlay); connect(m_colorOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_gradientOverlay = new GradientOverlay(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_gradientOverlay); connect(m_gradientOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_patternOverlay = new PatternOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_patternOverlay); connect(m_patternOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_stroke = new Stroke(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_stroke); connect(m_stroke, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); KisConfig cfg(true); wdgLayerStyles.stylesStack->setCurrentIndex(cfg.readEntry("KisDlgLayerStyle::current", 1)); wdgLayerStyles.lstStyleSelector->setCurrentRow(cfg.readEntry("KisDlgLayerStyle::current", 1)); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); // improve the checkbox visibility by altering the style sheet list a bit // the dark themes make them hard to see QPalette newPalette = palette(); newPalette.setColor(QPalette::Active, QPalette::Background, palette().text().color() ); wdgLayerStyles.lstStyleSelector->setPalette(newPalette); notifyPredefinedStyleSelected(layerStyle); connect(m_dropShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_innerShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_bevelAndEmboss, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(wdgLayerStyles.btnNewStyle, SIGNAL(clicked()), SLOT(slotNewStyle())); connect(wdgLayerStyles.btnLoadStyle, SIGNAL(clicked()), SLOT(slotLoadStyle())); connect(wdgLayerStyles.btnSaveStyle, SIGNAL(clicked()), SLOT(slotSaveStyle())); connect(wdgLayerStyles.chkMasterFxSwitch, SIGNAL(toggled(bool)), SLOT(slotMasterFxSwitchChanged(bool))); connect(this, SIGNAL(accepted()), SLOT(slotNotifyOnAccept())); connect(this, SIGNAL(rejected()), SLOT(slotNotifyOnReject())); } KisDlgLayerStyle::~KisDlgLayerStyle() { } void KisDlgLayerStyle::slotMasterFxSwitchChanged(bool value) { wdgLayerStyles.lstStyleSelector->setEnabled(value); wdgLayerStyles.stylesStack->setEnabled(value); wdgLayerStyles.btnNewStyle->setEnabled(value); wdgLayerStyles.btnLoadStyle->setEnabled(value); wdgLayerStyles.btnSaveStyle->setEnabled(value); notifyGuiConfigChanged(); } void KisDlgLayerStyle::notifyGuiConfigChanged() { if (m_isSwitchingPredefinedStyle) return; m_configChangedCompressor->start(); m_layerStyle->setUuid(QUuid::createUuid()); m_sanityLayerStyleDirty = true; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } void KisDlgLayerStyle::notifyPredefinedStyleSelected(KisPSDLayerStyleSP style) { m_isSwitchingPredefinedStyle = true; setStyle(style); m_isSwitchingPredefinedStyle = false; m_configChangedCompressor->start(); } void KisDlgLayerStyle::slotBevelAndEmbossChanged(QListWidgetItem*) { QListWidgetItem *item; if (wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked) { // Enable "Contour" (list item 7) item = wdgLayerStyles.lstStyleSelector->item(7); Qt::ItemFlags currentFlags7 = item->flags(); item->setFlags(currentFlags7 | Qt::ItemIsEnabled); // Enable "Texture" (list item 8) item = wdgLayerStyles.lstStyleSelector->item(8); Qt::ItemFlags currentFlags8 = item->flags(); item->setFlags(currentFlags8 | Qt::ItemIsEnabled); } else { // Disable "Contour" item = wdgLayerStyles.lstStyleSelector->item(7); Qt::ItemFlags currentFlags7 = item->flags(); item->setFlags(currentFlags7 & (~Qt::ItemIsEnabled)); // Disable "Texture" item = wdgLayerStyles.lstStyleSelector->item(8); Qt::ItemFlags currentFlags8 = item->flags(); item->setFlags(currentFlags8 & (~Qt::ItemIsEnabled)); } } void KisDlgLayerStyle::slotNotifyOnAccept() { if (m_configChangedCompressor->isActive()) { m_configChangedCompressor->stop(); emit configChanged(); } } void KisDlgLayerStyle::slotNotifyOnReject() { notifyPredefinedStyleSelected(m_initialLayerStyle); m_configChangedCompressor->stop(); emit configChanged(); } bool checkCustomNameAvailable(const QString &name) { const QString customName = "CustomStyles.asl"; KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); KoResource *resource = server->resourceByName(customName); if (!resource) return true; KisPSDLayerStyleCollectionResource *collection = dynamic_cast(resource); Q_FOREACH (KisPSDLayerStyleSP style, collection->layerStyles()) { if (style->name() == name) { return false; } } return true; } QString selectAvailableStyleName(const QString &name) { QString finalName = name; if (checkCustomNameAvailable(finalName)) { return finalName; } int i = 0; do { finalName = QString("%1%2").arg(name).arg(i++); } while (!checkCustomNameAvailable(finalName)); return finalName; } void KisDlgLayerStyle::slotNewStyle() { QString styleName = QInputDialog::getText(this, i18nc("@title:window", "Enter new style name"), i18nc("@label:textbox", "Name:"), QLineEdit::Normal, i18nc("Default name for a new style", "New Style")); KisPSDLayerStyleSP style = this->style(); style->setName(selectAvailableStyleName(styleName)); m_stylesSelector->addNewStyle(style->clone()); } void KisDlgLayerStyle::slotLoadStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::OpenFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); m_stylesSelector->loadCollection(filename); wdgLayerStyles.lstStyleSelector->setCurrentRow(0); } void KisDlgLayerStyle::slotSaveStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::SaveFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); QScopedPointer collection( new KisPSDLayerStyleCollectionResource(filename)); KisPSDLayerStyleSP newStyle = style()->clone(); - newStyle->setName(QFileInfo(filename).baseName()); + newStyle->setName(QFileInfo(filename).completeBaseName()); KisPSDLayerStyleCollectionResource::StylesVector vector = collection->layerStyles(); vector << newStyle; collection->setLayerStyles(vector); collection->save(); } void KisDlgLayerStyle::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) { current = previous; } wdgLayerStyles.stylesStack->setCurrentIndex(wdgLayerStyles.lstStyleSelector->row(current)); } void KisDlgLayerStyle::setStyle(KisPSDLayerStyleSP style) { // we may self-assign style is some cases if (style != m_layerStyle) { *m_layerStyle = *style; } m_sanityLayerStyleDirty = false; { KisSignalsBlocker b(m_stylesSelector); m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } QListWidgetItem *item; item = wdgLayerStyles.lstStyleSelector->item(2); item->setCheckState(m_layerStyle->dropShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(3); item->setCheckState(m_layerStyle->innerShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(4); item->setCheckState(m_layerStyle->outerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(5); item->setCheckState(m_layerStyle->innerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(6); item->setCheckState(m_layerStyle->bevelAndEmboss()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(7); item->setCheckState(m_layerStyle->bevelAndEmboss()->contourEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(8); item->setCheckState(m_layerStyle->bevelAndEmboss()->textureEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(9); item->setCheckState(m_layerStyle->satin()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(10); item->setCheckState(m_layerStyle->colorOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(11); item->setCheckState(m_layerStyle->gradientOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(12); item->setCheckState(m_layerStyle->patternOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(13); item->setCheckState(m_layerStyle->stroke()->effectEnabled() ? Qt::Checked : Qt::Unchecked); m_dropShadow->setShadow(m_layerStyle->dropShadow()); m_innerShadow->setShadow(m_layerStyle->innerShadow()); m_outerGlow->setConfig(m_layerStyle->outerGlow()); m_innerGlow->setConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->setBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->setSatin(m_layerStyle->satin()); m_colorOverlay->setColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->setGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->setPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->setStroke(m_layerStyle->stroke()); wdgLayerStyles.chkMasterFxSwitch->setChecked(m_layerStyle->isEnabled()); slotMasterFxSwitchChanged(m_layerStyle->isEnabled()); } KisPSDLayerStyleSP KisDlgLayerStyle::style() const { m_layerStyle->setEnabled(wdgLayerStyles.chkMasterFxSwitch->isChecked()); m_layerStyle->dropShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(2)->checkState() == Qt::Checked); m_layerStyle->innerShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(3)->checkState() == Qt::Checked); m_layerStyle->outerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(4)->checkState() == Qt::Checked); m_layerStyle->innerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(5)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setContourEnabled(wdgLayerStyles.lstStyleSelector->item(7)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setTextureEnabled(wdgLayerStyles.lstStyleSelector->item(8)->checkState() == Qt::Checked); m_layerStyle->satin()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(9)->checkState() == Qt::Checked); m_layerStyle->colorOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(10)->checkState() == Qt::Checked); m_layerStyle->gradientOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(11)->checkState() == Qt::Checked); m_layerStyle->patternOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(12)->checkState() == Qt::Checked); m_layerStyle->stroke()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(13)->checkState() == Qt::Checked); m_dropShadow->fetchShadow(m_layerStyle->dropShadow()); m_innerShadow->fetchShadow(m_layerStyle->innerShadow()); m_outerGlow->fetchConfig(m_layerStyle->outerGlow()); m_innerGlow->fetchConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->fetchBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->fetchSatin(m_layerStyle->satin()); m_colorOverlay->fetchColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->fetchGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->fetchPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->fetchStroke(m_layerStyle->stroke()); m_sanityLayerStyleDirty = false; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); return m_layerStyle; } void KisDlgLayerStyle::syncGlobalAngle(int angle) { KisPSDLayerStyleSP style = this->style(); if (style->dropShadow()->useGlobalLight()) { style->dropShadow()->setAngle(angle); } if (style->innerShadow()->useGlobalLight()) { style->innerShadow()->setAngle(angle); } if (style->bevelAndEmboss()->useGlobalLight()) { style->bevelAndEmboss()->setAngle(angle); } setStyle(style); } /********************************************************************/ /***** Styles Selector **********************************************/ /********************************************************************/ class StyleItem : public QListWidgetItem { public: StyleItem(KisPSDLayerStyleSP style) : QListWidgetItem(style->name()) , m_style(style) { } public: KisPSDLayerStyleSP m_style; }; StylesSelector::StylesSelector(QWidget *parent) : QWidget(parent) { ui.setupUi(this); connect(ui.cmbStyleCollections, SIGNAL(activated(QString)), this, SLOT(loadStyles(QString))); connect(ui.listStyles, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(selectStyle(QListWidgetItem*,QListWidgetItem*))); refillCollections(); if (ui.cmbStyleCollections->count()) { ui.cmbStyleCollections->setCurrentIndex(0); loadStyles(ui.cmbStyleCollections->currentText()); } } void StylesSelector::refillCollections() { QString previousCollection = ui.cmbStyleCollections->currentText(); ui.cmbStyleCollections->clear(); Q_FOREACH (KoResource *res, KisResourceServerProvider::instance()->layerStyleCollectionServer()->resources()) { ui.cmbStyleCollections->addItem(res->name()); } if (!previousCollection.isEmpty()) { KisSignalsBlocker blocker(this); int index = ui.cmbStyleCollections->findText(previousCollection); ui.cmbStyleCollections->setCurrentIndex(index); } } void StylesSelector::notifyExternalStyleChanged(const QString &name, const QUuid &uuid) { int currentIndex = -1; for (int i = 0; i < ui.listStyles->count(); i++ ) { StyleItem *item = dynamic_cast(ui.listStyles->item(i)); QString itemName = item->m_style->name(); if (itemName == name) { bool isDirty = item->m_style->uuid() != uuid; if (isDirty) { itemName += "*"; } currentIndex = i; } item->setText(itemName); } ui.listStyles->setCurrentRow(currentIndex); } void StylesSelector::loadStyles(const QString &name) { ui.listStyles->clear(); KoResource *res = KisResourceServerProvider::instance()->layerStyleCollectionServer()->resourceByName(name); KisPSDLayerStyleCollectionResource *collection = dynamic_cast(res); if (collection) { Q_FOREACH (KisPSDLayerStyleSP style, collection->layerStyles()) { // XXX: also use the preview image, when we have one ui.listStyles->addItem(new StyleItem(style)); } } } void StylesSelector::selectStyle(QListWidgetItem *current, QListWidgetItem* /*previous*/) { StyleItem *item = dynamic_cast(current); if (item) { emit styleSelected(item->m_style); } } void StylesSelector::loadCollection(const QString &fileName) { if (!QFileInfo(fileName).exists()) { warnKrita << "Loaded style collection doesn't exist!"; return; } KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource(fileName); collection->load(); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); collection->setFilename(server->saveLocation() + QDir::separator() + collection->name()); server->addResource(collection); refillCollections(); int index = ui.cmbStyleCollections->findText(collection->name()); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(collection->name()); } void StylesSelector::addNewStyle(KisPSDLayerStyleSP style) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); // NOTE: not translatable, since it is a key! const QString customName = "CustomStyles.asl"; const QString saveLocation = server->saveLocation(); const QString fullFilename = saveLocation + customName; KoResource *resource = server->resourceByName(customName); KisPSDLayerStyleCollectionResource *collection = 0; if (!resource) { collection = new KisPSDLayerStyleCollectionResource(""); collection->setName(customName); collection->setFilename(fullFilename); KisPSDLayerStyleCollectionResource::StylesVector vector; vector << style; collection->setLayerStyles(vector); server->addResource(collection); } else { collection = dynamic_cast(resource); KisPSDLayerStyleCollectionResource::StylesVector vector; vector = collection->layerStyles(); vector << style; collection->setLayerStyles(vector); collection->save(); } refillCollections(); // select in gui int index = ui.cmbStyleCollections->findText(customName); KIS_ASSERT_RECOVER_RETURN(index >= 0); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(customName); notifyExternalStyleChanged(style->name(), style->uuid()); } /********************************************************************/ /***** Bevel and Emboss *********************************************/ /********************************************************************/ BevelAndEmboss::BevelAndEmboss(Contour *contour, Texture *texture, QWidget *parent) : QWidget(parent) , m_contour(contour) , m_texture(texture) { ui.setupUi(this); // Structure ui.intDepth->setRange(0, 100); ui.intDepth->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intSoften->setRange(0, 18); ui.intSoften->setSuffix(i18n(" px")); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbDirection, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSoften, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Shading ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intOpacity2->setRange(0, 100); ui.intOpacity2->setSuffix(i18n(" %")); ui.angleSelector->enableGlobalLight(true); connect(ui.angleSelector, SIGNAL(globalAngleChanged(int)), SIGNAL(globalAngleChanged(int))); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.intAltitude, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbHighlightMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnHighlightColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbShadowMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnShadowColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Contour m_contour->ui.intRange->setRange(1, 100); m_contour->ui.intRange->setSuffix(i18n(" %")); connect(m_contour->ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(m_contour->ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_contour->ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Texture m_texture->ui.intScale->setRange(0, 100); m_texture->ui.intScale->setSuffix(i18n(" %")); m_texture->ui.intDepth->setRange(-1000, 1000); m_texture->ui.intDepth->setSuffix(i18n(" %")); connect(m_texture->ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(m_texture->ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_texture->ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void BevelAndEmboss::setBevelAndEmboss(const psd_layer_effects_bevel_emboss *bevelAndEmboss) { ui.cmbStyle->setCurrentIndex((int)bevelAndEmboss->style()); ui.cmbTechnique->setCurrentIndex((int)bevelAndEmboss->technique()); ui.intDepth->setValue(bevelAndEmboss->depth()); ui.cmbDirection->setCurrentIndex((int)bevelAndEmboss->direction()); ui.intSize->setValue(bevelAndEmboss->size()); ui.intSoften->setValue(bevelAndEmboss->soften()); ui.angleSelector->setValue(bevelAndEmboss->angle()); ui.angleSelector->setUseGlobalLight(bevelAndEmboss->useGlobalLight()); ui.intAltitude->setValue(bevelAndEmboss->altitude()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(bevelAndEmboss->glossAntiAliased()); ui.cmbHighlightMode->selectCompositeOp(KoID(bevelAndEmboss->highlightBlendMode())); KoColor highlightshadow(KoColorSpaceRegistry::instance()->rgb8()); highlightshadow.fromQColor(bevelAndEmboss->highlightColor()); ui.bnHighlightColor->setColor(highlightshadow); ui.intOpacity->setValue(bevelAndEmboss->highlightOpacity()); ui.cmbShadowMode->selectCompositeOp(KoID(bevelAndEmboss->shadowBlendMode())); highlightshadow.fromQColor(bevelAndEmboss->shadowColor()); ui.bnShadowColor->setColor(highlightshadow); ui.intOpacity2->setValue(bevelAndEmboss->shadowOpacity()); // FIXME: curve editing // m_contour->ui.cmbContour; m_contour->ui.chkAntiAliased->setChecked(bevelAndEmboss->antiAliased()); m_contour->ui.intRange->setValue(bevelAndEmboss->contourRange()); m_texture->ui.patternChooser->setCurrentPattern(bevelAndEmboss->texturePattern()); m_texture->ui.intScale->setValue(bevelAndEmboss->textureScale()); m_texture->ui.intDepth->setValue(bevelAndEmboss->textureDepth()); m_texture->ui.chkInvert->setChecked(bevelAndEmboss->textureInvert()); m_texture->ui.chkLinkWithLayer->setChecked(bevelAndEmboss->textureAlignWithLayer()); } void BevelAndEmboss::fetchBevelAndEmboss(psd_layer_effects_bevel_emboss *bevelAndEmboss) const { bevelAndEmboss->setStyle((psd_bevel_style)ui.cmbStyle->currentIndex()); bevelAndEmboss->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); bevelAndEmboss->setDepth(ui.intDepth->value()); bevelAndEmboss->setDirection((psd_direction)ui.cmbDirection->currentIndex()); bevelAndEmboss->setSize(ui.intSize->value()); bevelAndEmboss->setSoften(ui.intSoften->value()); bevelAndEmboss->setAngle(ui.angleSelector->value()); bevelAndEmboss->setUseGlobalLight(ui.angleSelector->useGlobalLight()); bevelAndEmboss->setAltitude(ui.intAltitude->value()); bevelAndEmboss->setGlossAntiAliased(ui.chkAntiAliased->isChecked()); bevelAndEmboss->setHighlightBlendMode(ui.cmbHighlightMode->selectedCompositeOp().id()); bevelAndEmboss->setHighlightColor(ui.bnHighlightColor->color().toQColor()); bevelAndEmboss->setHighlightOpacity(ui.intOpacity->value()); bevelAndEmboss->setShadowBlendMode(ui.cmbShadowMode->selectedCompositeOp().id()); bevelAndEmboss->setShadowColor(ui.bnShadowColor->color().toQColor()); bevelAndEmboss->setShadowOpacity(ui.intOpacity2->value()); // FIXME: curve editing bevelAndEmboss->setAntiAliased(m_contour->ui.chkAntiAliased->isChecked()); bevelAndEmboss->setContourRange(m_contour->ui.intRange->value()); bevelAndEmboss->setTexturePattern(static_cast(m_texture->ui.patternChooser->currentResource())); bevelAndEmboss->setTextureScale(m_texture->ui.intScale->value()); bevelAndEmboss->setTextureDepth(m_texture->ui.intDepth->value()); bevelAndEmboss->setTextureInvert(m_texture->ui.chkInvert->isChecked()); bevelAndEmboss->setTextureAlignWithLayer(m_texture->ui.chkLinkWithLayer->isChecked()); } /********************************************************************/ /***** Texture *********************************************/ /********************************************************************/ Texture::Texture(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Contour *********************************************/ /********************************************************************/ Contour::Contour(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Blending Options *********************************************/ /********************************************************************/ BlendingOptions::BlendingOptions(QWidget *parent) : QWidget(parent) { ui.setupUi(this); // FIXME: Blend options are not implemented yet ui.grpBlendingOptions->setTitle(QString("%1 (%2)").arg(ui.grpBlendingOptions->title()).arg(i18n("Not Implemented Yet"))); ui.grpBlendingOptions->setEnabled(false); } /********************************************************************/ /***** Color Overlay *********************************************/ /********************************************************************/ ColorOverlay::ColorOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); } void ColorOverlay::setColorOverlay(const psd_layer_effects_color_overlay *colorOverlay) { ui.cmbCompositeOp->selectCompositeOp(KoID(colorOverlay->blendMode())); ui.intOpacity->setValue(colorOverlay->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(colorOverlay->color()); ui.bnColor->setColor(color); } void ColorOverlay::fetchColorOverlay(psd_layer_effects_color_overlay *colorOverlay) const { colorOverlay->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); colorOverlay->setOpacity(ui.intOpacity->value()); colorOverlay->setColor(ui.bnColor->color().toQColor()); } /********************************************************************/ /***** Drop Shadow **************************************************/ /********************************************************************/ DropShadow::DropShadow(Mode mode, QWidget *parent) : QWidget(parent), m_mode(mode) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 500); ui.intDistance->setSuffix(i18n(" px")); ui.intDistance->setExponentRatio(3.0); ui.intSpread->setRange(0, 100); ui.intSpread->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.angleSelector->enableGlobalLight(true); connect(ui.angleSelector, SIGNAL(globalAngleChanged(int)), SIGNAL(globalAngleChanged(int))); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); // connect everything to configChanged() signal connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSpread, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.chkLayerKnocksOutDropShadow, SIGNAL(toggled(bool)), SIGNAL(configChanged())); if (m_mode == InnerShadowMode) { ui.chkLayerKnocksOutDropShadow->setVisible(false); ui.grpMain->setTitle(i18n("Inner Shadow")); ui.lblSpread->setText(i18n("Choke:")); } } void DropShadow::setShadow(const psd_layer_effects_shadow_common *shadow) { ui.cmbCompositeOp->selectCompositeOp(KoID(shadow->blendMode())); ui.intOpacity->setValue(shadow->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(shadow->color()); ui.bnColor->setColor(color); ui.angleSelector->setValue(shadow->angle()); ui.angleSelector->setUseGlobalLight(shadow->useGlobalLight()); ui.intDistance->setValue(shadow->distance()); ui.intSpread->setValue(shadow->spread()); ui.intSize->setValue(shadow->size()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(shadow->antiAliased()); ui.intNoise->setValue(shadow->noise()); if (m_mode == DropShadowMode) { const psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); ui.chkLayerKnocksOutDropShadow->setChecked(shadow->knocksOut()); } } void DropShadow::fetchShadow(psd_layer_effects_shadow_common *shadow) const { shadow->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); shadow->setOpacity(ui.intOpacity->value()); shadow->setColor(ui.bnColor->color().toQColor()); shadow->setAngle(ui.angleSelector->value()); shadow->setUseGlobalLight(ui.angleSelector->useGlobalLight()); shadow->setDistance(ui.intDistance->value()); shadow->setSpread(ui.intSpread->value()); shadow->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; shadow->setAntiAliased(ui.chkAntiAliased->isChecked()); shadow->setNoise(ui.intNoise->value()); if (m_mode == DropShadowMode) { psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); realDropShadow->setKnocksOut(ui.chkLayerKnocksOutDropShadow->isChecked()); } } class GradientPointerConverter { public: static KoAbstractGradientSP resourceToStyle(KoAbstractGradient *gradient) { return gradient ? KoAbstractGradientSP(gradient->clone()) : KoAbstractGradientSP(); } static KoAbstractGradient* styleToResource(KoAbstractGradientSP gradient) { if (!gradient) return 0; KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); KoAbstractGradient *resource = server->resourceByMD5(gradient->md5()); if (!resource) { KoAbstractGradient *clone = gradient->clone(); clone->setName(findAvailableName(gradient->name())); server->addResource(clone, false); resource = clone; } return resource; } private: static QString findAvailableName(const QString &name) { KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); QString newName = name; int i = 0; while (server->resourceByName(newName)) { newName = QString("%1%2").arg(name).arg(i++); } return newName; } }; /********************************************************************/ /***** Gradient Overlay *********************************************/ /********************************************************************/ GradientOverlay::GradientOverlay(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void GradientOverlay::setGradientOverlay(const psd_layer_effects_gradient_overlay *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); KoAbstractGradient *gradient = fetchGradientLazy( GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(config->reverse()); ui.cmbStyle->setCurrentIndex((int)config->style()); ui.chkAlignWithLayer->setCheckable(config->alignWithLayer()); ui.angleSelector->setValue(config->angle()); ui.intScale->setValue(config->scale()); } void GradientOverlay::fetchGradientOverlay(psd_layer_effects_gradient_overlay *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setReverse(ui.chkReverse->isChecked()); config->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); config->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); config->setAngle(ui.angleSelector->value()); config->setScale(ui.intScale->value()); } /********************************************************************/ /***** Innner Glow *********************************************/ /********************************************************************/ InnerGlow::InnerGlow(Mode mode, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_mode(mode), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.intChoke->setRange(0, 100); ui.intChoke->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intRange->setRange(1, 100); ui.intRange->setSuffix(i18n(" %")); ui.intJitter->setRange(0, 100); ui.intJitter->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.radioColor, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.radioGradient, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbSource, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intChoke, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intJitter, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); if (m_mode == OuterGlowMode) { ui.cmbSource->hide(); ui.lblSource->hide(); ui.lblChoke->setText(i18nc("layer styles parameter", "Spread:")); } } void InnerGlow::setConfig(const psd_layer_effects_glow_common *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); ui.intNoise->setValue(config->noise()); ui.radioColor->setChecked(config->fillType() == psd_fill_solid_color); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(config->color()); ui.bnColor->setColor(color); ui.radioGradient->setChecked(config->fillType() == psd_fill_gradient); KoAbstractGradient *gradient = fetchGradientLazy( GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.cmbTechnique->setCurrentIndex((int)config->technique()); ui.intChoke->setValue(config->spread()); ui.intSize->setValue(config->size()); if (m_mode == InnerGlowMode) { const psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); ui.cmbSource->setCurrentIndex(iglow->source() == psd_glow_edge); } // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(config->antiAliased()); ui.intRange->setValue(config->range()); ui.intJitter->setValue(config->jitter()); } void InnerGlow::fetchConfig(psd_layer_effects_glow_common *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setNoise(ui.intNoise->value()); if (ui.radioColor->isChecked()) { config->setFillType(psd_fill_solid_color); } else { config->setFillType(psd_fill_gradient); } config->setColor(ui.bnColor->color().toQColor()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); config->setSpread(ui.intChoke->value()); config->setSize(ui.intSize->value()); if (m_mode == InnerGlowMode) { psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); iglow->setSource((psd_glow_source)ui.cmbSource->currentIndex()); } // FIXME: Curve editing //ui.cmbContour; config->setAntiAliased(ui.chkAntiAliased->isChecked()); config->setRange(ui.intRange->value()); config->setJitter(ui.intJitter->value()); } /********************************************************************/ /***** Pattern Overlay *********************************************/ /********************************************************************/ PatternOverlay::PatternOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void PatternOverlay::setPatternOverlay(const psd_layer_effects_pattern_overlay *pattern) { ui.cmbCompositeOp->selectCompositeOp(KoID(pattern->blendMode())); ui.intOpacity->setValue(pattern->opacity()); ui.patternChooser->setCurrentPattern(pattern->pattern()); ui.chkLinkWithLayer->setChecked(pattern->alignWithLayer()); ui.intScale->setValue(pattern->scale()); } void PatternOverlay::fetchPatternOverlay(psd_layer_effects_pattern_overlay *pattern) const { pattern->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); pattern->setOpacity(ui.intOpacity->value()); pattern->setPattern(static_cast(ui.patternChooser->currentResource())); pattern->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); pattern->setScale(ui.intScale->value()); } /********************************************************************/ /***** Satin *********************************************/ /********************************************************************/ Satin::Satin(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 250); ui.intDistance->setSuffix(i18n(" px")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void Satin::setSatin(const psd_layer_effects_satin *satin) { ui.cmbCompositeOp->selectCompositeOp(KoID(satin->blendMode())); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(satin->color()); ui.bnColor->setColor(color); ui.intOpacity->setValue(satin->opacity()); ui.angleSelector->setValue(satin->angle()); ui.intDistance->setValue(satin->distance()); ui.intSize->setValue(satin->size()); // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(satin->antiAliased()); ui.chkInvert->setChecked(satin->invert()); } void Satin::fetchSatin(psd_layer_effects_satin *satin) const { satin->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); satin->setOpacity(ui.intOpacity->value()); satin->setColor(ui.bnColor->color().toQColor()); satin->setAngle(ui.angleSelector->value()); satin->setDistance(ui.intDistance->value()); satin->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; satin->setAntiAliased(ui.chkAntiAliased->isChecked()); satin->setInvert(ui.chkInvert->isChecked()); } /********************************************************************/ /***** Stroke *********************************************/ /********************************************************************/ Stroke::Stroke(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); ui.intScale_2->setRange(0, 100); ui.intScale_2->setSuffix(i18n(" %")); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), ui.fillStack, SLOT(setCurrentIndex(int))); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbPosition, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale_2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // cold initialization ui.fillStack->setCurrentIndex(ui.cmbFillType->currentIndex()); } void Stroke::setStroke(const psd_layer_effects_stroke *stroke) { ui.intSize->setValue(stroke->size()); ui.cmbPosition->setCurrentIndex((int)stroke->position()); ui.cmbCompositeOp->selectCompositeOp(KoID(stroke->blendMode())); ui.intOpacity->setValue(stroke->opacity()); ui.cmbFillType->setCurrentIndex((int)stroke->fillType()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(stroke->color()); ui.bnColor->setColor(color); KoAbstractGradient *gradient = fetchGradientLazy(GradientPointerConverter::styleToResource(stroke->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(stroke->antiAliased()); ui.cmbStyle->setCurrentIndex((int)stroke->style()); ui.chkAlignWithLayer->setCheckable(stroke->alignWithLayer()); ui.angleSelector->setValue(stroke->angle()); ui.intScale->setValue(stroke->scale()); ui.patternChooser->setCurrentPattern(stroke->pattern()); ui.chkLinkWithLayer->setChecked(stroke->alignWithLayer()); ui.intScale_2->setValue(stroke->scale()); } void Stroke::fetchStroke(psd_layer_effects_stroke *stroke) const { stroke->setSize(ui.intSize->value()); stroke->setPosition((psd_stroke_position)ui.cmbPosition->currentIndex()); stroke->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); stroke->setOpacity(ui.intOpacity->value()); stroke->setFillType((psd_fill_type)ui.cmbFillType->currentIndex()); stroke->setColor(ui.bnColor->color().toQColor()); stroke->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); stroke->setReverse(ui.chkReverse->isChecked()); stroke->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); stroke->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); stroke->setAngle(ui.angleSelector->value()); stroke->setScale(ui.intScale->value()); stroke->setPattern(static_cast(ui.patternChooser->currentResource())); stroke->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); stroke->setScale(ui.intScale->value()); } diff --git a/libs/ui/dialogs/kis_dlg_png_import.cpp b/libs/ui/dialogs/kis_dlg_png_import.cpp index 386b6c6c0f..9474695e13 100644 --- a/libs/ui/dialogs/kis_dlg_png_import.cpp +++ b/libs/ui/dialogs/kis_dlg_png_import.cpp @@ -1,64 +1,64 @@ /* * Copyright (C) 2015 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 "kis_dlg_png_import.h" #include #include #include #include #include #include #include #include "kis_config.h" KisDlgPngImport::KisDlgPngImport(const QString &path, const QString &colorModelID, const QString &colorDepthID, QWidget *parent) : KoDialog(parent) { setButtons(Ok); setDefaultButton(Ok); QWidget *page = new QWidget(this); dlgWidget.setupUi(page); setMainWidget(page); dlgWidget.lblFilename->setText(path); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID); dlgWidget.cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { dlgWidget.cmbProfile->addSqueezedItem(stringName); } KisConfig cfg(true); QString profile = cfg.readEntry("pngImportProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); dlgWidget.cmbProfile->setCurrent(profile); } QString KisDlgPngImport::profile() const { - QString p = dlgWidget.cmbProfile->itemHighlighted(); + QString p = dlgWidget.cmbProfile->currentUnsqueezedText(); KisConfig cfg(false); cfg.writeEntry("pngImportProfile", p); return p; } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index aeeac0d8a7..72a90fa2fe 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1708 +1,1740 @@ /* * 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 #include #include "KoID.h" #include #include #include #include #include #include #include "kis_action_registry.h" #include #include #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" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" # ifndef USE_QT_TABLET_WINDOWS # include # endif #include "config-high-dpi-scale-factor-rounding-policy.h" #endif struct BackupSuffixValidator : public QValidator { BackupSuffixValidator(QObject *parent) : QValidator(parent) , invalidCharacters(QStringList() << "0" << "1" << "2" << "3" << "4" << "5" << "6" << "7" << "8" << "9" << "/" << "\\" << ":" << ";" << " ") {} ~BackupSuffixValidator() override {} const QStringList invalidCharacters; State validate(QString &line, int &/*pos*/) const override { Q_FOREACH(const QString invalidChar, invalidCharacters) { if (line.contains(invalidChar)) { return Invalid; } } return Acceptable; } }; GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg(true); // // Cursor Tab // 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_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); 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_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); // // Window Tab // m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); QString xml = cfg.getMDIBackgroundColor(); KoColor mdiColor = KoColor::fromXML(xml); m_mdiColor->setColor(mdiColor); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", true).toBool()); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY m_chkHiDPIFractionalScaling->setChecked(kritarc.value("EnableHiDPIFractionalScaling", true).toBool()); #else m_chkHiDPIFractionalScaling->setVisible(false); #endif chkUsageLogging->setChecked(kritarc.value("LogUsage", true).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); cmbFlowMode->setCurrentIndex((int)!cfg.readEntry("useCreamyAlphaDarken", true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste()); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled()); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Middle-Click Drag")); //m_cmbKineticScrollingGesture->addItem(i18n("On Right Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivitySlider->setRange(0, 100); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars()); // // File handling // int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); chkHideAutosaveFiles->setChecked(cfg.readEntry("autosavefileshidden", true)); m_chkCompressKra->setChecked(cfg.compressKra()); chkZip64->setChecked(cfg.useZip64()); m_backupFileCheckBox->setChecked(cfg.backupFile()); cmbBackupFileLocation->setCurrentIndex(cfg.readEntry("backupfilelocation", 0)); txtBackupFileSuffix->setText(cfg.readEntry("backupfilesuffix", "~")); QValidator *validator = new BackupSuffixValidator(txtBackupFileSuffix); txtBackupFileSuffix->setValidator(validator); intNumBackupFiles->setValue(cfg.readEntry("numberofbackupfiles", 1)); // // Miscellaneous // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); } void GeneralTab::setDefault() { KisConfig cfg(true); 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); chkHideAutosaveFiles->setChecked(true); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); cmbBackupFileLocation->setCurrentIndex(0); txtBackupFileSuffix->setText("~"); intNumBackupFiles->setValue(1); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline(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.fromXML(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)); chkZip64->setChecked(cfg.useZip64(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY m_chkHiDPIFractionalScaling->setChecked(true); #endif chkUsageLogging->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); cmbFlowMode->setCurrentIndex(0); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(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(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } 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(); } 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::useZip64() { return chkZip64->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::kineticScrollingEnabled() { return m_groupBoxKineticScrollingSettings->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivitySlider->value(); } bool GeneralTab::kineticScrollingHiddenScrollbars() { return m_chkKineticScrollingHideScrollbars->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->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(true); 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; KisSqueezedComboBox *cmb = new KisSqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } // disable if not Linux as KisColorManager is not yet implemented outside Linux #ifndef Q_OS_LINUX m_page->chkUseSystemMonitorProfile->setChecked(false); m_page->chkUseSystemMonitorProfile->setDisabled(true); m_page->chkUseSystemMonitorProfile->setHidden(true); #endif 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()); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors()); KisImageConfig cfgImage(true); 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(true); 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) { KisConfig cfg(true); if (useSystemProfile) { 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 { 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(true); KisImageConfig cfgImage(true); 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->chkForcePaletteColor->setChecked(cfg.forcePaletteColors(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); m_page->chkUseRightMiddleClickWorkaround->setChecked( KisConfig(true).useRightMiddleTabletButtonWorkaround(true)); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { KisConfig cfg(true); 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); } #else m_page->grpTabletApi->setVisible(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(true); KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); m_page->chkUseRightMiddleClickWorkaround->setChecked( cfg.useRightMiddleTabletButtonWorkaround()); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { 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); } #ifdef USE_QT_TABLET_WINDOWS connect(m_page->btnResolutionSettings, SIGNAL(clicked()), SLOT(slotResolutionSettings())); connect(m_page->radioWintab, SIGNAL(toggled(bool)), m_page->btnResolutionSettings, SLOT(setEnabled(bool))); m_page->btnResolutionSettings->setEnabled(m_page->radioWintab->isChecked()); #else m_page->btnResolutionSettings->setVisible(false); #endif #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS #include "KisDlgCustomTabletResolution.h" #endif void TabletSettingsTab::slotResolutionSettings() { #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS KisDlgCustomTabletResolution dlg(this); dlg.exec(); #endif } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { return KisImageConfig(true).totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg(true); const double totalRAM = cfg.totalRAM(); lblTotalMemory->setText(KFormat().formatByteSize(totalRAM * 1024 * 1024, 0, KFormat::IECBinaryDialect, KFormat::UnitMegaByte)); 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))); intCachedFramesSizeLimit->setRange(1, 10000); intCachedFramesSizeLimit->setSuffix(i18n(" px")); intCachedFramesSizeLimit->setSingleStep(1); intCachedFramesSizeLimit->setPageStep(1000); intRegionOfInterestMargin->setRange(1, 100); intRegionOfInterestMargin->setSuffix(i18n(" %")); intRegionOfInterestMargin->setSingleStep(1); intRegionOfInterestMargin->setPageStep(10); connect(chkCachedFramesSizeLimit, SIGNAL(toggled(bool)), intCachedFramesSizeLimit, SLOT(setEnabled(bool))); connect(chkUseRegionOfInterest, SIGNAL(toggled(bool)), intRegionOfInterestMargin, SLOT(setEnabled(bool))); #ifndef Q_OS_WIN // AVX workaround is needed on Windows+GCC only chkDisableAVXOptimizations->setVisible(false); #endif load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg(true); 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); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2(true); chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); #ifdef Q_OS_WIN chkDisableAVXOptimizations->setChecked(cfg2.disableAVXOptimizations(requestDefault)); #endif chkBackgroundCacheGeneration->setChecked(cfg2.calculateAnimationCacheInBackground(requestDefault)); } if (cfg.useOnDiskAnimationCacheSwapping(requestDefault)) { optOnDisk->setChecked(true); } else { optInMemory->setChecked(true); } chkCachedFramesSizeLimit->setChecked(cfg.useAnimationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setValue(cfg.animationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setEnabled(chkCachedFramesSizeLimit->isChecked()); chkUseRegionOfInterest->setChecked(cfg.useAnimationCacheRegionOfInterest(requestDefault)); intRegionOfInterestMargin->setValue(cfg.animationCacheRegionOfInterestMargin(requestDefault) * 100.0); intRegionOfInterestMargin->setEnabled(chkUseRegionOfInterest->isChecked()); } void PerformanceTab::save() { KisImageConfig cfg(false); 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(true); cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); #ifdef Q_OS_WIN cfg2.setDisableAVXOptimizations(chkDisableAVXOptimizations->isChecked()); #endif cfg2.setCalculateAnimationCacheInBackground(chkBackgroundCacheGeneration->isChecked()); } cfg.setUseOnDiskAnimationCacheSwapping(optOnDisk->isChecked()); cfg.setUseAnimationCacheFrameSizeLimit(chkCachedFramesSizeLimit->isChecked()); cfg.setAnimationCacheFrameSizeLimit(intCachedFramesSizeLimit->value()); cfg.setUseAnimationCacheRegionOfInterest(chkUseRegionOfInterest->isChecked()); cfg.setAnimationCacheRegionOfInterestMargin(intRegionOfInterestMargin->value() / 100.0); } void PerformanceTab::selectSwapDir() { KisImageConfig cfg(true); 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; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" #include "opengl/KisOpenGLModeProber.h" #include "opengl/KisScreenInformationAdapter.h" #include #include QString colorSpaceString(KisSurfaceColorSpace cs, int depth) { const QString csString = #ifdef HAVE_HDR cs == KisSurfaceColorSpace::bt2020PQColorSpace ? "Rec. 2020 PQ" : cs == KisSurfaceColorSpace::scRGBColorSpace ? "Rec. 709 Linear" : #endif cs == KisSurfaceColorSpace::sRGBColorSpace ? "sRGB" : cs == KisSurfaceColorSpace::DefaultColorSpace ? "sRGB" : "Unknown Color Space"; return QString("%1 (%2 bit)").arg(csString).arg(depth); } int formatToIndex(KisConfig::RootSurfaceFormat fmt) { return fmt == KisConfig::BT2020_PQ ? 1 : fmt == KisConfig::BT709_G10 ? 2 : 0; } KisConfig::RootSurfaceFormat indexToFormat(int value) { return value == 1 ? KisConfig::BT2020_PQ : value == 2 ? KisConfig::BT709_G10 : KisConfig::BT709_G22; } DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); + const QString rendererSoftwareText = i18nc("canvas renderer", "Software Renderer (very slow)"); #ifdef Q_OS_WIN const QString rendererOpenGLESText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #else const QString rendererOpenGLESText = i18nc("canvas renderer", "OpenGL ES"); #endif - lblCurrentRenderer->setText(KisOpenGL::hasOpenGLES() ? rendererOpenGLESText : rendererOpenGLText); + + const KisOpenGL::OpenGLRenderer renderer = KisOpenGL::getCurrentOpenGLRenderer(); + lblCurrentRenderer->setText(renderer == KisOpenGL::RendererOpenGLES ? rendererOpenGLESText : + renderer == KisOpenGL::RendererDesktopGL ? rendererOpenGLText : + renderer == KisOpenGL::RendererSoftware ? rendererSoftwareText : + i18nc("canvas renderer", "Unknown")); cmbPreferredRenderer->clear(); - QString qtPreferredRendererText; - if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { - qtPreferredRendererText = rendererOpenGLESText; + + const KisOpenGL::OpenGLRenderers supportedRenderers = KisOpenGL::getSupportedOpenGLRenderers(); + const bool onlyOneRendererSupported = + supportedRenderers == KisOpenGL::RendererDesktopGL || + supportedRenderers == KisOpenGL::RendererOpenGLES || + supportedRenderers == KisOpenGL::RendererSoftware; + + + if (!onlyOneRendererSupported) { + QString qtPreferredRendererText; + if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { + qtPreferredRendererText = rendererOpenGLESText; + } else if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererSoftware) { + qtPreferredRendererText = rendererSoftwareText; + } else { + qtPreferredRendererText = rendererOpenGLText; + } + cmbPreferredRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); + cmbPreferredRenderer->setCurrentIndex(0); } else { - qtPreferredRendererText = rendererOpenGLText; + cmbPreferredRenderer->setEnabled(false); } - cmbPreferredRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); - cmbPreferredRenderer->setCurrentIndex(0); - if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { + + if (supportedRenderers & KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #ifdef Q_OS_WIN - if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererOpenGLES) { + if (supportedRenderers & KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->addItem(rendererOpenGLESText, KisOpenGL::RendererOpenGLES); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } + if (supportedRenderers & KisOpenGL::RendererSoftware) { + cmbPreferredRenderer->addItem(rendererSoftwareText, KisOpenGL::RendererSoftware); + if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererSoftware) { + cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); + } + } #endif - if (!(KisOpenGL::getSupportedOpenGLRenderers() & - (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { + if (!(supportedRenderers & + (KisOpenGL::RendererDesktopGL | + KisOpenGL::RendererOpenGLES | + KisOpenGL::RendererSoftware))) { + 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); } } lblCurrentDisplayFormat->setText(""); lblCurrentRootSurfaceFormat->setText(""); lblHDRWarning->setText(""); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::sRGBColorSpace, 8)); #ifdef HAVE_HDR cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::bt2020PQColorSpace, 10)); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::scRGBColorSpace, 16)); #endif cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); QOpenGLContext *context = QOpenGLContext::currentContext(); if (!context) { context = QOpenGLContext::globalShareContext(); } if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (screen && adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { QStringList toolTip; toolTip << i18n("Display Id: %1", info.screen->name()); toolTip << i18n("Display Name: %1 %2", info.screen->manufacturer(), info.screen->model()); toolTip << i18n("Min Luminance: %1", info.minLuminance); toolTip << i18n("Max Luminance: %1", info.maxLuminance); toolTip << i18n("Max Full Frame Luminance: %1", info.maxFullFrameLuminance); toolTip << i18n("Red Primary: %1, %2", info.redPrimary[0], info.redPrimary[1]); toolTip << i18n("Green Primary: %1, %2", info.greenPrimary[0], info.greenPrimary[1]); toolTip << i18n("Blue Primary: %1, %2", info.bluePrimary[0], info.bluePrimary[1]); toolTip << i18n("White Point: %1, %2", info.whitePoint[0], info.whitePoint[1]); lblCurrentDisplayFormat->setToolTip(toolTip.join('\n')); lblCurrentDisplayFormat->setText(colorSpaceString(info.colorSpace, info.bitsPerColor)); } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); } } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); qWarning() << "Failed to fetch display info:" << adapter.errorString(); } const QSurfaceFormat currentFormat = KisOpenGLModeProber::instance()->surfaceformatInUse(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace colorSpace = currentFormat.colorSpace(); #else KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif lblCurrentRootSurfaceFormat->setText(colorSpaceString(colorSpace, currentFormat.redBufferSize())); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(cfg.rootSurfaceFormat())); connect(cmbPreferedRootSurfaceFormat, SIGNAL(currentIndexChanged(int)), SLOT(slotPreferredSurfaceFormatChanged(int))); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } #ifndef HAVE_HDR grpHDRSettings->setVisible(false); #endif const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("

    "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
  • "); text.append(warning.toHtmlEscaped()); text.append("
  • "); } text.append("
"); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(imageCfg.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(true); cmbPreferredRenderer->setCurrentIndex(0); if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { 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); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } void DisplaySettingsTab::slotPreferredSurfaceFormatChanged(int index) { Q_UNUSED(index); QOpenGLContext *context = QOpenGLContext::currentContext(); if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { if (cmbPreferedRootSurfaceFormat->currentIndex() != formatToIndex(KisConfig::BT709_G22) && info.colorSpace == KisSurfaceColorSpace::sRGBColorSpace) { lblHDRWarning->setText(i18n("WARNING: current display doesn't support HDR rendering")); } else { lblHDRWarning->setText(""); } } } } } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(true); 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(true); 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); 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")); m_pages << page; 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")); m_pages << page; 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")); m_pages << page; // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); m_pages << page; 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")); m_pages << page; 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")); m_pages << page; 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")); m_pages << page; 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")); m_pages << page; 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")); m_pages << page; QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(i18nc("@action:button", "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())); KisConfig cfg(true); QString currentPageName = cfg.readEntry("KisDlgPreferences/CurrentPage"); Q_FOREACH(KPageWidgetItem *page, m_pages) { if (page->objectName() == currentPageName) { setCurrentPage(page); break; } } } KisDlgPreferences::~KisDlgPreferences() { KisConfig cfg(true); cfg.writeEntry("KisDlgPreferences/CurrentPage", currentPage()->objectName()); } void KisDlgPreferences::showEvent(QShowEvent *event){ KPageDialog::showEvent(event); button(QDialogButtonBox::Cancel)->setAutoDefault(false); button(QDialogButtonBox::Ok)->setAutoDefault(false); button(QDialogButtonBox::RestoreDefaults)->setAutoDefault(false); button(QDialogButtonBox::Cancel)->setDefault(false); button(QDialogButtonBox::Ok)->setDefault(false); button(QDialogButtonBox::RestoreDefaults)->setDefault(false); } 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(false); 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.setForceAlwaysFullSizedOutline(!dialog->m_general->m_changeBrushOutline->isChecked()); cfg.setSessionOnStartup(dialog->m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(dialog->m_general->saveSessionOnQuit()); 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().toXML()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.writeEntry("autosavefileshidden", dialog->m_general->chkHideAutosaveFiles->isChecked()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.writeEntry("backupfilelocation", dialog->m_general->cmbBackupFileLocation->currentIndex()); cfg.writeEntry("backupfilesuffix", dialog->m_general->txtBackupFileSuffix->text()); cfg.writeEntry("numberofbackupfiles", dialog->m_general->intNumBackupFiles->value()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); cfg.setUseZip64(dialog->m_general->useZip64()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY kritarc.setValue("EnableHiDPIFractionalScaling", dialog->m_general->m_chkHiDPIFractionalScaling->isChecked()); #endif kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); kritarc.setValue("LogUsage", dialog->m_general->chkUsageLogging->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.writeEntry("useCreamyAlphaDarken", (bool)!dialog->m_general->cmbFlowMode->currentIndex()); cfg.setKineticScrollingEnabled(dialog->m_general->kineticScrollingEnabled()); cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingHideScrollbars(dialog->m_general->kineticScrollingHiddenScrollbars()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setActivateTransformToolAfterPaste(dialog->m_general->chkEnableTranformToolAfterPaste->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_monitorProfileWidgets[i]->currentUnsqueezedText(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(false); 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.setForcePaletteColors(dialog->m_colorSettings->m_page->chkForcePaletteColor->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() ); cfg.setUseRightMiddleTabletButtonWorkaround( dialog->m_tabletSettings->m_page->chkUseRightMiddleClickWorkaround->isChecked()); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); - { + if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) + cfg.setCanvasState("TRY_OPENGL"); + + if (dialog->m_displaySettings->grpOpenGL->isChecked()) { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbPreferredRenderer->itemData( dialog->m_displaySettings->cmbPreferredRenderer->currentIndex()).toInt()); KisOpenGL::setUserPreferredOpenGLRendererConfig(renderer); + } else { + KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::RendererNone); } - 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.setRootSurfaceFormat(&kritarc, indexToFormat(dialog->m_displaySettings->cmbPreferedRootSurfaceFormat->currentIndex())); 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()); cfgImage.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/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp index efd7c9c96a..6e2d31722f 100644 --- a/libs/ui/flake/kis_shape_controller.cpp +++ b/libs/ui/flake/kis_shape_controller.cpp @@ -1,287 +1,288 @@ /* * 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_shape_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "kis_adjustment_layer.h" #include "kis_clone_layer.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_group_layer.h" #include "kis_node_shape.h" #include "kis_node_shapes_graph.h" #include "kis_name_server.h" #include "kis_mask.h" #include "kis_shape_layer.h" #include "KisViewManager.h" #include "kis_node.h" #include #include #include #include #include "KoSelectedShapesProxy.h" #include "kis_signal_auto_connection.h" struct KisShapeController::Private { public: KisDocument *doc; KisNameServer *nameServer; KisSignalAutoConnectionsStore imageConnections; KisNodeShapesGraph shapesGraph; }; KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer) : KisDummiesFacadeBase(doc) , m_d(new Private()) { m_d->doc = doc; m_d->nameServer = nameServer; resourceManager()->setUndoStack(doc->undoStack()); } KisShapeController::~KisShapeController() { KisNodeDummy *node = m_d->shapesGraph.rootDummy(); if (node) { m_d->shapesGraph.removeNode(node->node()); } delete m_d; } void KisShapeController::slotUpdateDocumentResolution() { const qreal pixelsPerInch = m_d->doc->image()->xRes() * 72.0; resourceManager()->setResource(KoDocumentResourceManager::DocumentResolution, pixelsPerInch); } void KisShapeController::slotUpdateDocumentSize() { resourceManager()->setResource(KoDocumentResourceManager::DocumentRectInPixels, m_d->doc->image()->bounds()); } void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { KisNodeShape *newShape = m_d->shapesGraph.addNode(node, parent, aboveThis); // XXX: what are we going to do with this shape? Q_UNUSED(newShape); KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { /** * Forward signals for global shape manager * \see comment in the constructor of KisCanvas2 */ connect(shapeLayer, SIGNAL(selectionChanged()), SIGNAL(selectionChanged())); connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()), SIGNAL(selectionContentChanged())); connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)), SIGNAL(currentLayerChanged(const KoShapeLayer*))); } } void KisShapeController::removeNodeImpl(KisNodeSP node) { KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { shapeLayer->disconnect(this); } m_d->shapesGraph.removeNode(node); } bool KisShapeController::hasDummyForNode(KisNodeSP node) const { return m_d->shapesGraph.containsNode(node); } KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const { return m_d->shapesGraph.nodeToDummy(node); } KisNodeDummy* KisShapeController::rootDummy() const { return m_d->shapesGraph.rootDummy(); } int KisShapeController::dummiesCount() const { return m_d->shapesGraph.shapesCount(); } static inline bool belongsToShapeSelection(KoShape* shape) { return dynamic_cast(shape->userData()); } void KisShapeController::addShapes(const QList shapes) { KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty()); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); const KoShape *baseShapeParent = shapes.first()->parent(); const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first()); bool allSameParent = true; bool allSameBelongsToShapeSelection = true; bool hasNullParent = false; Q_FOREACH (KoShape *shape, shapes) { hasNullParent |= !shape->parent(); allSameParent &= shape->parent() == baseShapeParent; allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection; } KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection); if (!allSameParent || hasNullParent) { if (baseBelongsToSelection && allSameBelongsToShapeSelection) { KisSelectionSP selection = canvas->viewManager()->selection(); if (selection) { if (!selection->shapeSelection()) { selection->setShapeSelection(new KisShapeSelection(this, image(), selection)); } KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); Q_FOREACH(KoShape *shape, shapes) { shapeSelection->addShape(shape); } } } else { KisShapeLayer *shapeLayer = dynamic_cast( canvas->selectedShapesProxy()->selection()->activeLayer()); if (!shapeLayer) { shapeLayer = new KisShapeLayer(this, image(), i18n("Vector Layer %1", m_d->nameServer->number()), OPACITY_OPAQUE_U8); image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount())); } QRectF updateRect; Q_FOREACH(KoShape *shape, shapes) { shapeLayer->addShape(shape); updateRect |= shape->boundingRect(); } canvas->shapeManager()->update(updateRect); } } m_d->doc->setModified(true); } void KisShapeController::removeShape(KoShape* shape) { /** * Krita layers have their own destruction path. * It goes through slotRemoveNode() */ Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID && shape->shapeId() != KIS_SHAPE_LAYER_ID); QRectF updateRect = shape->boundingRect(); shape->setParent(0); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); canvas->shapeManager()->update(updateRect); m_d->doc->setModified(true); } QRectF KisShapeController::documentRectInPixels() const { return m_d->doc->image()->bounds(); } qreal KisShapeController::pixelsPerInch() const { return m_d->doc->image()->xRes() * 72.0; } void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas) { if (!image()) return; KisNodeSP rootNode = image()->root(); if (m_d->shapesGraph.containsNode(rootNode)) { Q_ASSERT(canvas); Q_ASSERT(canvas->shapeManager()); KoSelection *selection = canvas->shapeManager()->selection(); if (selection && m_d->shapesGraph.nodeToShape(rootNode)) { selection->select(m_d->shapesGraph.nodeToShape(rootNode)); KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes())); } } } void KisShapeController::setImage(KisImageWSP image) { m_d->imageConnections.clear(); - m_d->imageConnections.addConnection(m_d->doc->image(), SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotUpdateDocumentResolution())); - m_d->imageConnections.addConnection(m_d->doc->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)), this, SLOT(slotUpdateDocumentSize())); - + if (image) { + m_d->imageConnections.addConnection(image, SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotUpdateDocumentResolution())); + m_d->imageConnections.addConnection(image, SIGNAL(sigSizeChanged(QPointF, QPointF)), this, SLOT(slotUpdateDocumentSize())); + } slotUpdateDocumentResolution(); slotUpdateDocumentSize(); KisDummiesFacadeBase::setImage(image); } KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const { if (node) { return m_d->shapesGraph.nodeToShape(node); } return 0; } diff --git a/libs/ui/input/kis_abstract_input_action.cpp b/libs/ui/input/kis_abstract_input_action.cpp index 561d327436..030dc4dae4 100644 --- a/libs/ui/input/kis_abstract_input_action.cpp +++ b/libs/ui/input/kis_abstract_input_action.cpp @@ -1,223 +1,232 @@ /* 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_abstract_input_action.h" #include #include #include #include class Q_DECL_HIDDEN KisAbstractInputAction::Private { public: QString id; QString name; QString description; QHash indexes; QPointF lastCursorPosition; + QPointF startCursorPosition; static KisInputManager *inputManager; }; KisInputManager *KisAbstractInputAction::Private::inputManager = 0; KisAbstractInputAction::KisAbstractInputAction(const QString &id) : d(new Private) { d->id = id; d->indexes.insert(i18n("Activate"), 0); } KisAbstractInputAction::~KisAbstractInputAction() { delete d; } void KisAbstractInputAction::activate(int shortcut) { Q_UNUSED(shortcut); } void KisAbstractInputAction::deactivate(int shortcut) { Q_UNUSED(shortcut); } void KisAbstractInputAction::begin(int shortcut, QEvent *event) { Q_UNUSED(shortcut); if (event) { d->lastCursorPosition = eventPosF(event); + d->startCursorPosition = d->lastCursorPosition; } } void KisAbstractInputAction::inputEvent(QEvent *event) { if (event) { QPointF newPosition = eventPosF(event); cursorMoved(d->lastCursorPosition, newPosition); + cursorMovedAbsolute(d->startCursorPosition, newPosition); d->lastCursorPosition = newPosition; } } void KisAbstractInputAction::end(QEvent *event) { Q_UNUSED(event); } void KisAbstractInputAction::cursorMoved(const QPointF &lastPos, const QPointF &pos) { Q_UNUSED(lastPos); Q_UNUSED(pos); } +void KisAbstractInputAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) +{ + Q_UNUSED(startPos); + Q_UNUSED(pos); +} + bool KisAbstractInputAction::supportsHiResInputEvents() const { return false; } KisInputActionGroup KisAbstractInputAction::inputActionGroup(int shortcut) const { Q_UNUSED(shortcut); return ModifyingActionGroup; } KisInputManager* KisAbstractInputAction::inputManager() const { return Private::inputManager; } QString KisAbstractInputAction::name() const { return d->name; } QString KisAbstractInputAction::description() const { return d->description; } int KisAbstractInputAction::priority() const { return 0; } bool KisAbstractInputAction::canIgnoreModifiers() const { return false; } QHash< QString, int > KisAbstractInputAction::shortcutIndexes() const { return d->indexes; } QString KisAbstractInputAction::id() const { return d->id; } void KisAbstractInputAction::setName(const QString &name) { d->name = name; } void KisAbstractInputAction::setDescription(const QString &description) { d->description = description; } void KisAbstractInputAction::setShortcutIndexes(const QHash< QString, int > &indexes) { d->indexes = indexes; } void KisAbstractInputAction::setInputManager(KisInputManager *manager) { Private::inputManager = manager; } bool KisAbstractInputAction::isShortcutRequired(int shortcut) const { Q_UNUSED(shortcut); return false; } QPoint KisAbstractInputAction::eventPos(const QEvent *event) { if(!event) { return QPoint(); } switch (event->type()) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: return static_cast(event)->pos(); case QEvent::TabletMove: case QEvent::TabletPress: case QEvent::TabletRelease: return static_cast(event)->pos(); case QEvent::Wheel: return static_cast(event)->pos(); case QEvent::NativeGesture: return static_cast(event)->pos(); default: warnInput << "KisAbstractInputAction" << d->name << "tried to process event data from an unhandled event type" << event->type(); return QPoint(); } } QPointF KisAbstractInputAction::eventPosF(const QEvent *event) { switch (event->type()) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: return static_cast(event)->localPos(); case QEvent::TabletMove: case QEvent::TabletPress: case QEvent::TabletRelease: return static_cast(event)->posF(); case QEvent::Wheel: return static_cast(event)->posF(); case QEvent::NativeGesture: return QPointF(static_cast(event)->pos()); default: warnInput << "KisAbstractInputAction" << d->name << "tried to process event data from an unhandled event type" << event->type(); return QPointF(); } } bool KisAbstractInputAction::isAvailable() const { return true; } diff --git a/libs/ui/input/kis_abstract_input_action.h b/libs/ui/input/kis_abstract_input_action.h index 539b77fca1..20699f2864 100644 --- a/libs/ui/input/kis_abstract_input_action.h +++ b/libs/ui/input/kis_abstract_input_action.h @@ -1,230 +1,231 @@ /* 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. */ #ifndef KIS_ABSTRACT_INPUT_ACTION_H #define KIS_ABSTRACT_INPUT_ACTION_H #include #include #include "kritaui_export.h" #include "KisInputActionGroup.h" class QPointF; class QEvent; class KisInputManager; /** * \brief Abstract base class for input actions. * * Input actions represent actions to be performed when interacting * with the canvas. They are managed by KisInputManager and activated * when KisKeyShortcut or KisStrokeShortcut detects it matches a certain * set of inputs. * * The begin() method uses an index for the type of behaviour to activate. * This index can be used to trigger behaviour when different events occur. * * The events can be of two types: * 1) Key events. The input manager calls begin() and end() sequentially * with an \p index parameter to begin() representing the type of * action that should be performed. The \p event parameter of both * calls in null. * 2) Stroke events. The input manager calls begin() and end() on the * corresponding mouse down and up events. The \p event parameter * will be of QMouseEvent type, representing the event happened. * All the mouse move events between begin() and end() will be * redirected to the inputEvent() method. */ class KRITAUI_EXPORT KisAbstractInputAction { public: /** * Constructor. * * \param manager The InputManager this action belongs to. */ explicit KisAbstractInputAction(const QString &id); /** * Destructor. */ virtual ~KisAbstractInputAction(); /** * The method is called when the action is yet to be started, * that is, e.g. the user has pressed all the modifiers for the * action but hasn't started painting yet. This method is a right * place to show the user what is going to happen, e.g. change the * cursor. */ virtual void activate(int shortcut); /** * The method is called when the action is not a candidate for * the starting anymore. The action should revert everything that * was done in activate() method. * * \see activate() */ virtual void deactivate(int shortcut); /** * Begin the action. * * \param shortcut The index of the behaviour to trigger. * \param event The mouse event that has triggered this action. * Is null for keyboard-activated actions. */ virtual void begin(int shortcut, QEvent *event); /** * End the action. * \param event The mouse event that has finished this action. * Is null for keyboard-activated actions. */ virtual void end(QEvent *event); /** * Process an input event. * * By default handles MouseMove events and passes the data to * a convenience cursorMoved() method * * \param event An event to process. */ virtual void inputEvent(QEvent* event); /** * Returns true if the action can handle HiRes flow of move events * which is generated by the tablet. If the function returns * false, some of the events will be dropped or postponed. For * most of the actions in Krita (except of real painting) it is * perfectly acceptable, so 'false' is the default value. */ virtual bool supportsHiResInputEvents() const; /** * \return the group of the action the specified \p shortcut belongs to */ virtual KisInputActionGroup inputActionGroup(int shortcut) const; /** * The indexes of shortcut behaviours available. */ virtual QHash shortcutIndexes() const; /** * The id of this action. */ virtual QString id() const; /** * The translated name of this action. */ virtual QString name() const; /** * A short description of this action. */ virtual QString description() const; /** * The priority for this action. * * Priority determines how "important" the action is and is used * to resolve conflicts when multiple actions can be activated. */ virtual int priority() const; /** * Returns true if an action can run with any modifiers pressed * (the shortcut's modifiers list must be empty for that). That is * used for making one type of actions default one. */ virtual bool canIgnoreModifiers() const; /** * Return true when the specified shortcut is required for basic * user interaction. This is used by the configuration system to * prevent basic actions like painting from being removed. * * \param shortcut The shortcut index to check. * \return True if the shortcut is required, false if not. */ virtual bool isShortcutRequired(int shortcut) const; /** * Some of the actions are available in particular situations * only. E.g. switch frame action is available iff a animated * layer is selected. If isAvailable() returns true then the * action will *not* be triggered by the shortcut matcher. */ virtual bool isAvailable() const; protected: /** * The input manager this action belongs to. */ KisInputManager *inputManager() const; /** * Set the name of this action. * * \param name The new name. */ void setName(const QString &name); /** * Set the description of this action. * * \param description The new description. */ void setDescription(const QString &description); /** * Set the available indexes of shortcut behaviours. * * \param indexes The new indexes. */ void setShortcutIndexes(const QHash &indexes); /** * Convenience method for handling cursor movement for tablet, mouse and touch. * The default implementation of inputEvent calls this function. */ virtual void cursorMoved(const QPointF &lastPos, const QPointF &pos); + virtual void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos); /** * Convenience method to extract the position from a cursor movement event. * * \param event A mouse or tablet event. */ QPoint eventPos(const QEvent *event); /** * Convenience method to extract the floating point position from a * cursor movement event. * * \param event A mouse or tablet event. */ QPointF eventPosF(const QEvent *event); private: friend class KisInputManager; static void setInputManager(KisInputManager *manager); class Private; Private * const d; }; #endif // KIS_ABSTRACT_INPUT_ACTION_H diff --git a/libs/ui/input/kis_gamma_exposure_action.cpp b/libs/ui/input/kis_gamma_exposure_action.cpp index fd6e0bad0b..d14f333bdf 100644 --- a/libs/ui/input/kis_gamma_exposure_action.cpp +++ b/libs/ui/input/kis_gamma_exposure_action.cpp @@ -1,199 +1,199 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_gamma_exposure_action.h" #include #include #include #include #include "kis_cursor.h" #include "KisViewManager.h" #include "kis_input_manager.h" #include "krita_utils.h" #include "kis_exposure_gamma_correction_interface.h" class KisGammaExposureAction::Private { public: Private(KisGammaExposureAction *qq) : q(qq), baseExposure(0.0), baseGamma(0.0) {} KisGammaExposureAction *q; Shortcuts mode; qreal baseExposure; qreal baseGamma; void addExposure(qreal diff); void addGamma(qreal diff); }; void KisGammaExposureAction::Private::addExposure(qreal diff) { KisExposureGammaCorrectionInterface *interface = q->inputManager()->canvas()->exposureGammaCorrectionInterface(); if (!interface->canChangeExposureAndGamma()) return; interface->setCurrentExposure(interface->currentExposure() + diff); } void KisGammaExposureAction::Private::addGamma(qreal diff) { KisExposureGammaCorrectionInterface *interface = q->inputManager()->canvas()->exposureGammaCorrectionInterface(); if (!interface->canChangeExposureAndGamma()) return; interface->setCurrentGamma(interface->currentGamma() + diff); } KisGammaExposureAction::KisGammaExposureAction() : KisAbstractInputAction("Exposure or Gamma") , d(new Private(this)) { setName(i18n("Exposure and Gamma")); setDescription(i18n("The Exposure and Gamma action changes the display mode of the canvas.")); QHash< QString, int > shortcuts; shortcuts.insert(i18n("Exposure Mode"), ExposureShortcut); shortcuts.insert(i18n("Gamma Mode"), GammaShortcut); shortcuts.insert(i18n("Exposure +0.5"), AddExposure05Shortcut); shortcuts.insert(i18n("Exposure -0.5"), RemoveExposure05Shortcut); shortcuts.insert(i18n("Gamma +0.5"), AddGamma05Shortcut); shortcuts.insert(i18n("Gamma -0.5"), RemoveGamma05Shortcut); shortcuts.insert(i18n("Exposure +0.2"), AddExposure02Shortcut); shortcuts.insert(i18n("Exposure -0.2"), RemoveExposure02Shortcut); shortcuts.insert(i18n("Gamma +0.2"), AddGamma02Shortcut); shortcuts.insert(i18n("Gamma -0.2"), RemoveGamma02Shortcut); shortcuts.insert(i18n("Reset Exposure and Gamma"), ResetExposureAndGammaShortcut); setShortcutIndexes(shortcuts); } KisGammaExposureAction::~KisGammaExposureAction() { delete d; } int KisGammaExposureAction::priority() const { return 5; } void KisGammaExposureAction::activate(int shortcut) { if (shortcut == ExposureShortcut) { QApplication::setOverrideCursor(KisCursor::changeExposureCursor()); } else /* if (shortcut == GammaShortcut) */ { QApplication::setOverrideCursor(KisCursor::changeGammaCursor()); } } void KisGammaExposureAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisGammaExposureAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); KisExposureGammaCorrectionInterface *interface = inputManager()->canvas()->exposureGammaCorrectionInterface(); switch(shortcut) { case ExposureShortcut: d->baseExposure = interface->currentExposure(); d->mode = (Shortcuts)shortcut; break; case GammaShortcut: d->baseGamma = interface->currentGamma(); d->mode = (Shortcuts)shortcut; break; case AddExposure05Shortcut: d->addExposure(0.5); break; case RemoveExposure05Shortcut: d->addExposure(-0.5); break; case AddGamma05Shortcut: d->addGamma(0.5); break; case RemoveGamma05Shortcut: d->addGamma(-0.5); break; case AddExposure02Shortcut: d->addExposure(0.2); break; case RemoveExposure02Shortcut: d->addExposure(-0.2); break; case AddGamma02Shortcut: d->addGamma(0.2); break; case RemoveGamma02Shortcut: d->addGamma(-0.2); break; case ResetExposureAndGammaShortcut: { KisExposureGammaCorrectionInterface *interface = inputManager()->canvas()->exposureGammaCorrectionInterface(); if (!interface->canChangeExposureAndGamma()) break; interface->setCurrentGamma(1.0); interface->setCurrentExposure(0.0); break; } } } -void KisGammaExposureAction::cursorMoved(const QPointF &lastPos, const QPointF &pos) +void KisGammaExposureAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) { - QPointF diff = -(pos - lastPos); + QPointF diff = -(pos - startPos); const int step = 200; KisExposureGammaCorrectionInterface *interface = inputManager()->canvas()->exposureGammaCorrectionInterface(); if (!interface->canChangeExposureAndGamma()) return; if (d->mode == ExposureShortcut) { - d->baseExposure += qreal(diff.y()) / step; - interface->setCurrentExposure(d->baseExposure); + const qreal currentExposure = d->baseExposure + qreal(diff.y()) / step; + interface->setCurrentExposure(currentExposure); } else if (d->mode == GammaShortcut) { - d->baseGamma += qreal(diff.y()) / step; - interface->setCurrentGamma(d->baseGamma); + const qreal currentGamma = d->baseExposure + qreal(diff.y()) / step; + interface->setCurrentGamma(currentGamma); } } bool KisGammaExposureAction::isShortcutRequired(int shortcut) const { Q_UNUSED(shortcut); return false; } diff --git a/libs/ui/input/kis_gamma_exposure_action.h b/libs/ui/input/kis_gamma_exposure_action.h index 3aa46f1c15..38c15b2f2e 100644 --- a/libs/ui/input/kis_gamma_exposure_action.h +++ b/libs/ui/input/kis_gamma_exposure_action.h @@ -1,62 +1,62 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_GAMMA_EXPOSURE_ACTION_H #define __KIS_GAMMA_EXPOSURE_ACTION_H #include "kis_abstract_input_action.h" class KisGammaExposureAction : public KisAbstractInputAction { public: /** * The different behaviours for this action. */ enum Shortcuts { ExposureShortcut, GammaShortcut, AddExposure05Shortcut, RemoveExposure05Shortcut, AddGamma05Shortcut, RemoveGamma05Shortcut, AddExposure02Shortcut, RemoveExposure02Shortcut, AddGamma02Shortcut, RemoveGamma02Shortcut, ResetExposureAndGammaShortcut }; explicit KisGammaExposureAction(); ~KisGammaExposureAction() override; int priority() const override; void activate(int shortcut) override; void deactivate(int shortcut) override; void begin(int shortcut, QEvent *event = 0) override; - void cursorMoved(const QPointF &lastPos, const QPointF &pos) override; + void cursorMovedAbsolute(const QPointF &lastPos, const QPointF &pos) override; bool isShortcutRequired(int shortcut) const override; private: class Private; Private * const d; }; #endif /* __KIS_GAMMA_EXPOSURE_ACTION_H */ diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index 88d2331ab4..e357c7633c 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,761 +1,829 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager.h" #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_pan_action.h" #include "kis_alternate_invocation_action.h" #include "kis_rotate_canvas_action.h" #include "kis_zoom_action.h" #include "kis_show_palette_action.h" #include "kis_change_primary_setting_action.h" #include "kis_shortcut_matcher.h" #include "kis_stroke_shortcut.h" #include "kis_single_action_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile.h" #include "kis_input_profile_manager.h" #include "kis_shortcut_configuration.h" #include #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool())); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { d->allowMouseEvents(); } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &filter = it->second; if (filter.isNull()) { it = d->priorityEventFilter.erase(it); d->priorityEventFilterSeqNo++; savedPriorityEventFilterSeqNo++; continue; } if (filter->eventFilter(object, event)) return true; /** * If the filter removed itself from the filters list or * added something there, just exit the loop */ if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) { return true; } ++it; } // KoToolProxy needs to pre-process some events to ensure the // global shortcuts (not the input manager's ones) are not // executed, in particular, this line will accept events when the // tool is in text editing, preventing shortcut triggering if (d->toolProxy) { d->toolProxy->processEvent(event); } } // Continue with the actual switch statement... return eventFilterImpl(event); } // Qt's events do not have copy-ctors yes, so we should emulate them // See https://bugreports.qt.io/browse/QTBUG-72488 template void copyEventHack(Event *src, QScopedPointer &dst); template<> void copyEventHack(QMouseEvent *src, QScopedPointer &dst) { QMouseEvent *tmp = new QMouseEvent(src->type(), src->localPos(), src->windowPos(), src->screenPos(), src->button(), src->buttons(), src->modifiers(), src->source()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template<> void copyEventHack(QTabletEvent *src, QScopedPointer &dst) { QTabletEvent *tmp = new QTabletEvent(src->type(), src->posF(), src->globalPosF(), src->device(), src->pointerType(), src->pressure(), src->xTilt(), src->yTilt(), src->tangentialPressure(), src->rotation(), src->z(), src->modifiers(), src->uniqueId(), src->button(), src->buttons()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } +template<> void copyEventHack(QTouchEvent *src, QScopedPointer &dst) { + QTouchEvent *tmp = new QTouchEvent(src->type(), + src->device(), + src->modifiers(), + src->touchPointStates(), + src->touchPoints()); + tmp->setTimestamp(src->timestamp()); + dst.reset(tmp); +} template bool KisInputManager::compressMoveEventCommon(Event *event) { /** * We construct a copy of this event object, so we must ensure it * has a correct type. */ static_assert(std::is_same::value || - std::is_same::value, + std::is_same::value || + std::is_same::value, "event should be a mouse or a tablet event"); bool retval = false; /** * Compress the events if the tool doesn't need high resolution input */ if ((event->type() == QEvent::MouseMove || - event->type() == QEvent::TabletMove) && + event->type() == QEvent::TabletMove || + event->type() == QEvent::TouchUpdate) && (!d->matcher.supportsHiResInputEvents() || d->testingCompressBrushEvents)) { copyEventHack(event, d->compressedMoveEvent); d->moveEventCompressor.start(); /** * On Linux Qt eats the rest of unneeded events if we * ignore the first of the chunk of tablet events. So * generally we should never activate this feature. Only * for testing purposes! */ if (d->testingAcceptCompressedTabletEvents) { event->setAccepted(true); } retval = true; } else { slotCompressedMoveEvent(); retval = d->handleCompressedTabletEvent(event); } return retval; } bool shouldResetWheelDelta(QEvent * event) { return event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease || event->type() == QEvent::Enter || event->type() == QEvent::Leave || event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel || event->type() == QEvent::NativeGesture; } bool KisInputManager::eventFilterImpl(QEvent * event) { bool retval = false; if (shouldResetWheelDelta(event)) { d->accumulatedScrollDelta = 0; } switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent); } //Reset signal compressor to prevent processing events before press late d->resetCompressor(); event->setAccepted(retval); break; } case QEvent::MouseButtonRelease: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); if (!keyEvent->isAutoRepeat()) { retval = d->matcher.keyPressed(key); } else { retval = d->matcher.autoRepeatedKeyPressed(key); } /** * Workaround for temporary switching of tools by * KoCanvasControllerWidget. We don't need this switch because * we handle it ourselves. */ retval |= !d->forwardAllEventsToTool && (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Escape); break; } case QEvent::KeyRelease: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); QMouseEvent *mouseEvent = static_cast(event); retval = compressMoveEventCommon(mouseEvent); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); #ifdef Q_OS_MACOS // Some QT wheel events are actually touch pad pan events. From the QT docs: // "Wheel events are generated for both mouse wheels and trackpad scroll gestures." // We differentiate between touchpad events and real mouse wheels by inspecting the // event source. if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent); break; } #endif d->accumulatedScrollDelta += wheelEvent->delta(); KisSingleActionShortcut::WheelAction action; /** * Ignore delta 0 events on OSX, since they are triggered by tablet * proximity when using Wacom devices. */ #ifdef Q_OS_MACOS if(wheelEvent->delta() == 0) { retval = true; break; } #endif if (wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); d->accumulatedScrollDelta = 0; } else { retval = true; } break; } case QEvent::Enter: d->debugEvent(event); //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); if (!d->containsPointer) { d->containsPointer = true; d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; } d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); eventFilterImpl(&kevent); } } d->allowMouseEvents(); break; case QEvent::FocusOut: { d->debugEvent(event); KisAbstractInputAction::setInputManager(this); QPointF currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); d->matcher.lostFocusEvent(currentLocalPos); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); if (!d->containsPointer) { d->containsPointer = true; d->touchHasBlockedPressEvents = false; } } event->setAccepted(true); retval = true; d->blockMouseEvents(); //Reset signal compressor to prevent processing events before press late d->resetCompressor(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); d->eatOneMousePress(); #elif defined Q_OS_WIN32 /** * Windows is the only platform that synthesizes mouse events for * the tablet on OS-level, that is, even when we accept the event */ d->eatOneMousePress(); #endif break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = compressMoveEventCommon(tabletEvent); if (d->tabletLatencyTracker) { d->tabletLatencyTracker->push(tabletEvent->timestamp()); } /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the * TabletEnterProximity event was missed (may happen when * changing focus of the window with tablet in the proximity * area) */ d->blockMouseEvents(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TabletRelease: { #ifdef Q_OS_MAC d->allowMouseEvents(); #endif d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TouchBegin: { if (startTouch(retval)) { - QTouchEvent *tevent = static_cast(event); + QTouchEvent *touchEvent = static_cast (event); KisAbstractInputAction::setInputManager(this); - retval = d->matcher.touchBeginEvent(tevent); + + if (!KisConfig(true).disableTouchOnCanvas() + && touchEvent->touchPoints().count() == 1) + { + d->previousPos = touchEvent->touchPoints().at(0).pos(); + d->buttonPressed = false; + } + else { + retval = d->matcher.touchBeginEvent(touchEvent); + } event->accept(); } + + // if the event isn't handled, Qt starts to send MouseEvents + if (!KisConfig(true).disableTouchOnCanvas()) + retval = true; break; } + case QEvent::TouchUpdate: { - QTouchEvent *tevent = static_cast(event); + QTouchEvent *touchEvent = static_cast(event); + #ifdef Q_OS_MAC int count = 0; - Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { + Q_FOREACH (const QTouchEvent::TouchPoint &point, touchEvent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; } } - if (count < 2 && tevent->touchPoints().length() > count) { + if (count < 2 && touchEvent->touchPoints().length() > count) { d->touchHasBlockedPressEvents = false; - retval = d->matcher.touchEndEvent(tevent); + retval = d->matcher.touchEndEvent(touchEvent); } else { #endif - d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); - KisAbstractInputAction::setInputManager(this); - retval = d->matcher.touchUpdateEvent(tevent); + QPointF currentPos = touchEvent->touchPoints().at(0).pos(); + if (d->touchStrokeStarted || (!KisConfig(true).disableTouchOnCanvas() + && !d->touchHasBlockedPressEvents + && touchEvent->touchPoints().count() == 1 + && touchEvent->touchPointStates() != Qt::TouchPointStationary + && (qAbs(currentPos.x() - d->previousPos.x()) > 1 // stop wobbiliness which Qt sends us + || qAbs(currentPos.y() - d->previousPos.y()) > 1))) + { + d->previousPos = currentPos; + if (!d->buttonPressed) + { + // we start it here not in TouchBegin, because Qt::TouchPointStationary doesn't work with hpdi devices. + retval = d->matcher.buttonPressed(Qt::LeftButton, touchEvent); + d->buttonPressed = true; + break; + } + + // if it is a full-fledged stroke, then ignore (currentPos.x - previousPos.x) + d->touchStrokeStarted = true; + retval = compressMoveEventCommon(touchEvent); + d->blockMouseEvents(); + d->resetCompressor(); + } + else if (!d->touchStrokeStarted){ + KisAbstractInputAction::setInputManager(this); + + retval = d->matcher.touchUpdateEvent(touchEvent); + d->touchHasBlockedPressEvents = retval; + } #ifdef Q_OS_MACOS } #endif + // if the event isn't handled, Qt starts to send MouseEvents + if (!KisConfig(true).disableTouchOnCanvas()) + retval = true; + event->accept(); break; } + case QEvent::TouchEnd: { endTouch(); - QTouchEvent *tevent = static_cast(event); - retval = d->matcher.touchEndEvent(tevent); + QTouchEvent *touchEvent = static_cast(event); + retval = d->matcher.touchEndEvent(touchEvent); + if (d->touchStrokeStarted) + { + retval = d->matcher.buttonReleased(Qt::LeftButton, touchEvent); + + d->previousPos = {0, 0}; + d->touchStrokeStarted = false; // stroke ended + } + + // if the event isn't handled, Qt starts to send MouseEvents + if (!KisConfig(true).disableTouchOnCanvas()) + retval = true; + event->accept(); break; } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); switch (gevent->gestureType()) { case Qt::BeginNativeGesture: { if (startTouch(retval)) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureBeginEvent(gevent); event->accept(); } break; } case Qt::EndNativeGesture: { endTouch(); retval = d->matcher.nativeGestureEndEvent(gevent); event->accept(); break; } default: { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureEvent(gevent); event->accept(); break; } } break; } default: break; } return !retval ? d->processUnhandledEvent(event) : true; } bool KisInputManager::startTouch(bool &retval) { - d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); // Touch rejection: if touch is disabled on canvas, no need to block mouse press events if (KisConfig(true).disableTouchOnCanvas()) { d->eatOneMousePress(); } if (d->tryHidePopupPalette()) { retval = true; return false; } else { return true; } } void KisInputManager::endTouch() { d->touchHasBlockedPressEvents = false; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // d->touchHasBlockedPressEvents = false; (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); //dbgInput << "Compressed move event received."; } else { //dbgInput << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } QPointer KisInputManager::toolProxy() const { return d->toolProxy; } void KisInputManager::slotAboutToChangeTool() { QPointF currentLocalPos; if (canvas() && canvas()->canvasWidget()) { currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); } d->matcher.lostFocusEvent(currentLocalPos); } void KisInputManager::slotToolChanged() { if (!d->canvas) return; KoToolManager *toolManager = KoToolManager::instance(); KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId()); if (tool) { d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); if (tool->isInTextMode()) { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } } } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList shortcuts = profile->allShortcuts(); for (KisShortcutConfiguration * const shortcut : shortcuts) { dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name(); switch(shortcut->type()) { case KisShortcutConfiguration::KeyCombinationType: d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys()); break; case KisShortcutConfiguration::MouseButtonType: d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons()); break; case KisShortcutConfiguration::MouseWheelType: d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel()); break; case KisShortcutConfiguration::GestureType: if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) { d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture()); } break; default: break; } } } else { dbgInput << "No Input Profile Found: canvas interaction will be impossible"; } } diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp index 7df52bd4e0..085e089ff1 100644 --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -1,669 +1,673 @@ /* * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager_p.h" #include #include #include #include #include "kis_input_manager.h" #include "kis_config.h" #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #include "kis_input_profile_manager.h" #include "kis_extended_modifiers_mapper.h" +#include "kis_zoom_and_rotate_action.h" + /** * This hungry class EventEater encapsulates event masking logic. * * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after * tablet events. Those events are sent in order to allow widgets that haven't * implemented tablet specific functionality to seamlessly behave as if one were * using a mouse. These synthetic events are *supposed* to be optional, or at * least come with a flag saying "This is a fake event!!" but neither of those * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.) * * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered * over the pad, since it converts all tablethover events into mousemove, with * no option to turn this off. Moreover, sometimes the MouseButtonPress event * from the tapping their tablet happens BEFORE the TabletPress event. This * means we have to resort to a somewhat complicated logic. What makes this * truly a joke is that we are not guaranteed to observe TabletProximityEnter * events when we're using a tablet, either, you may only see an Enter event. * * Once we see tablet events heading our way, we can say pretty confidently that * every mouse event is fake. There are two painful cases to consider - a * mousePress event could arrive before the tabletPress event, or it could * arrive much later, e.g. after tabletRelease. The first was only seen on Linux * with Qt's XInput2 code, the solution was to hold onto mousePress events * temporarily and wait for tabletPress later, this is contained in git history * but is now removed. The second case is currently handled by the * eatOneMousePress function, which waits as long as necessary to detect and * block a single mouse press event. */ static bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick); } KisInputManager::Private::EventEater::EventEater() { KisConfig cfg(true); activateSecondaryButtonsWorkaround = cfg.useRightMiddleTabletButtonWorkaround(); } bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { Q_UNUSED(target) auto debugEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QMouseEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; auto debugTabletEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QTabletEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; if (peckish && event->type() == QEvent::MouseButtonPress // Drop one mouse press following tabletPress or touchBegin && (static_cast(event)->button() == Qt::LeftButton)) { peckish = false; debugEvent(1); return true; } if (activateSecondaryButtonsWorkaround) { if (event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *te = static_cast(event); if (te->button() != Qt::LeftButton) { debugTabletEvent(3); return true; } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick) { QMouseEvent *me = static_cast(event); if (me->button() != Qt::LeftButton) { return false; } } } if (isMouseEventType(event->type()) && (hungry // On Mac, we need mouse events when the tablet is in proximity, but not pressed down // since tablet move events are not generated until after tablet press. #ifndef Q_OS_MAC || (eatSyntheticEvents && static_cast(event)->source() != Qt::MouseEventNotSynthesized) #endif )) { // Drop mouse events if enabled or event was synthetic & synthetic events are disabled debugEvent(2); return true; } return false; // All clear - let this one through! } void KisInputManager::Private::EventEater::activate() { if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Start blocking mouse events"; } hungry = true; } void KisInputManager::Private::EventEater::deactivate() { if (hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Stop blocking mouse events"; } hungry = false; } void KisInputManager::Private::EventEater::eatOneMousePress() { // Enable on other platforms if getting full-pressure splotches peckish = true; } bool KisInputManager::Private::ignoringQtCursorEvents() { return eventEater.hungry; } void KisInputManager::Private::setMaskSyntheticEvents(bool value) { eventEater.eatSyntheticEvents = value; } KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE) , priorityEventFilterSeqNo(0) , canvasSwitcher(this, qq) { KisConfig cfg(true); moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); if (cfg.trackTabletEventLatency()) { tabletLatencyTracker = new TabletLatencyTracker(); } matcher.setInputActionGroupsMaskCallback( [this] () { return this->canvas ? this->canvas->inputActionGroupsMask() : AllActionGroup; }); } static const int InputWidgetsThreshold = 2000; static const int OtherWidgetsThreshold = 400; KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false), focusSwitchThreshold(InputWidgetsThreshold) { } void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object) { QWidget *widget = qobject_cast(object); KIS_SAFE_ASSERT_RECOVER_RETURN(widget); thresholdConnections.clear(); thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus())); } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { if (!canvas) return; QObject *canvasWidget = canvas->canvasWidget(); if (!canvasResolver.contains(canvasWidget)) { canvasResolver.insert(canvasWidget, canvas); d->q->setupAsEventFilter(canvasWidget); canvasWidget->installEventFilter(this); setupFocusThreshold(canvasWidget); focusSwitchThreshold.setEnabled(false); d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); } else { KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas); } } void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas) { QObject *widget = canvas->canvasWidget(); canvasResolver.remove(widget); if (d->eventsReceiver == widget) { d->q->setupAsEventFilter(0); } widget->removeEventFilter(this); } bool isInputWidget(QWidget *w) { if (!w) return false; QList types; types << QLatin1String("QAbstractSlider"); types << QLatin1String("QAbstractSpinBox"); types << QLatin1String("QLineEdit"); types << QLatin1String("QTextEdit"); types << QLatin1String("QPlainTextEdit"); types << QLatin1String("QComboBox"); types << QLatin1String("QKeySequenceEdit"); Q_FOREACH (const QLatin1String &type, types) { if (w->inherits(type.data())) { return true; } } return false; } bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event ) { if (canvasResolver.contains(object)) { switch (event->type()) { case QEvent::FocusIn: { QFocusEvent *fevent = static_cast(event); KisCanvas2 *canvas = canvasResolver.value(object); // only relevant canvases from the same main window should be // registered in the switcher KIS_SAFE_ASSERT_RECOVER_BREAK(canvas); if (canvas != d->canvas) { eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason); } d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); d->q->setupAsEventFilter(object); object->removeEventFilter(this); object->installEventFilter(this); setupFocusThreshold(object); focusSwitchThreshold.setEnabled(false); const QPoint globalPos = QCursor::pos(); const QPoint localPos = d->canvas->canvasWidget()->mapFromGlobal(globalPos); QWidget *canvasWindow = d->canvas->canvasWidget()->window(); const QPoint windowsPos = canvasWindow ? canvasWindow->mapFromGlobal(globalPos) : localPos; QEnterEvent event(localPos, windowsPos, globalPos); d->q->eventFilter(object, &event); break; } case QEvent::FocusOut: { focusSwitchThreshold.setEnabled(true); break; } case QEvent::Enter: { break; } case QEvent::Leave: { focusSwitchThreshold.stop(); break; } case QEvent::Wheel: { QWidget *widget = static_cast(object); widget->setFocus(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::TabletPress: case QEvent::TabletRelease: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { eatOneMouseStroke--; return true; } break; case QEvent::MouseButtonDblClick: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { return true; } break; case QEvent::MouseMove: case QEvent::TabletMove: { QWidget *widget = static_cast(object); if (!widget->hasFocus()) { const int delay = isInputWidget(QApplication::focusWidget()) ? InputWidgetsThreshold : OtherWidgetsThreshold; focusSwitchThreshold.setDelayThreshold(delay); focusSwitchThreshold.start(); } } break; default: break; } } return QObject::eventFilter(object, event); } KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p) : QObject(p), d(_d) {} bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event ) { /** * All Qt builds in range 5.7.0...5.11.X on X11 had a problem that made all * the tablet events be accepted by default. It meant that no mouse * events were synthesized, and, therefore, no Enter/Leave were generated. * * The fix for this bug has been added only in Qt 5.12.0: * https://codereview.qt-project.org/#/c/239918/ * * To avoid this problem we should explicitly ignore all the tablet events. */ #if defined Q_OS_LINUX && \ QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) && \ QT_VERSION < QT_VERSION_CHECK(5, 12, 0) if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { event->ignore(); } #endif switch (event->type()) { case QEvent::TabletEnterProximity: d->debugEvent(event); // Tablet proximity events are unreliable AND fake mouse events do not // necessarily come after tablet events, so this is insufficient. // d->eventEater.eatOneMousePress(); // Qt sends fake mouse events instead of hover events, so not very useful. // Don't block mouse events on tablet since tablet move events are not generated until // after tablet press. #ifndef Q_OS_MACOS d->blockMouseEvents(); #endif break; case QEvent::TabletLeaveProximity: d->debugEvent(event); d->allowMouseEvents(); break; default: break; } return QObject::eventFilter(object, event); } void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, Qt::MouseButtons buttons) { KisStrokeShortcut *strokeShortcut = new KisStrokeShortcut(action, index); QList buttonList; if(buttons & Qt::LeftButton) { buttonList << Qt::LeftButton; } if(buttons & Qt::RightButton) { buttonList << Qt::RightButton; } if(buttons & Qt::MidButton) { buttonList << Qt::MidButton; } if(buttons & Qt::XButton1) { buttonList << Qt::XButton1; } if(buttons & Qt::XButton2) { buttonList << Qt::XButton2; } if (buttonList.size() > 0) { strokeShortcut->setButtons(QSet::fromList(modifiers), QSet::fromList(buttonList)); matcher.addShortcut(strokeShortcut); } else { delete strokeShortcut; } } void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index, const QList &keys) { if (keys.size() == 0) return; KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); //Note: Ordering is important here, Shift + V is different from V + Shift, //which is the reason we use the last key here since most users will enter //shortcuts as "Shift + V". Ideally this should not happen, but this is //the way the shortcut matcher is currently implemented. QList allKeys = keys; Qt::Key key = allKeys.takeLast(); QSet modifiers = QSet::fromList(allKeys); keyShortcut->setKey(modifiers, key); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction) { QScopedPointer keyShortcut( new KisSingleActionShortcut(action, index)); KisSingleActionShortcut::WheelAction a; switch(wheelAction) { case KisShortcutConfiguration::WheelUp: a = KisSingleActionShortcut::WheelUp; break; case KisShortcutConfiguration::WheelDown: a = KisSingleActionShortcut::WheelDown; break; case KisShortcutConfiguration::WheelLeft: a = KisSingleActionShortcut::WheelLeft; break; case KisShortcutConfiguration::WheelRight: a = KisSingleActionShortcut::WheelRight; break; case KisShortcutConfiguration::WheelTrackpad: a = KisSingleActionShortcut::WheelTrackpad; break; default: return; } keyShortcut->setWheel(QSet::fromList(modifiers), a); matcher.addShortcut(keyShortcut.take()); } void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { KisTouchShortcut *shortcut = new KisTouchShortcut(action, index); switch(gesture) { + case KisShortcutConfiguration::RotateGesture: case KisShortcutConfiguration::PinchGesture: + shortcut = new KisTouchShortcut(new KisZoomAndRotateAction, index); shortcut->setMinimumTouchPoints(2); shortcut->setMaximumTouchPoints(2); break; case KisShortcutConfiguration::PanGesture: shortcut->setMinimumTouchPoints(3); shortcut->setMaximumTouchPoints(10); break; default: break; } matcher.addShortcut(shortcut); } bool KisInputManager::Private::addNativeGestureShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { // Qt5 only implements QNativeGestureEvent for macOS Qt::NativeGestureType type; switch (gesture) { #ifdef Q_OS_MACOS case KisShortcutConfiguration::PinchGesture: type = Qt::ZoomNativeGesture; break; case KisShortcutConfiguration::PanGesture: type = Qt::PanNativeGesture; break; case KisShortcutConfiguration::RotateGesture: type = Qt::RotateNativeGesture; break; case KisShortcutConfiguration::SmartZoomGesture: type = Qt::SmartZoomNativeGesture; break; #endif default: return false; } KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type); matcher.addShortcut(shortcut); return true; } void KisInputManager::Private::setupActions() { QList actions = KisInputProfileManager::instance()->actions(); Q_FOREACH (KisAbstractInputAction *action, actions) { KisToolInvocationAction *toolAction = dynamic_cast(action); if(toolAction) { defaultInputAction = toolAction; } } connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged())); if(KisInputProfileManager::instance()->currentProfile()) { q->profileChanged(); } } bool KisInputManager::Private::processUnhandledEvent(QEvent *event) { bool retval = false; if (forwardAllEventsToTool || event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { defaultInputAction->processUnhandledEvent(event); retval = true; } return retval && !forwardAllEventsToTool; } bool KisInputManager::Private::tryHidePopupPalette() { if (canvas->isPopupPaletteVisible()) { canvas->slotShowPopupPalette(); return true; } return false; } #ifdef HAVE_X11 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y()); } inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y()); } #endif void KisInputManager::Private::blockMouseEvents() { eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { eventEater.deactivate(); } void KisInputManager::Private::eatOneMousePress() { eventEater.eatOneMousePress(); } void KisInputManager::Private::resetCompressor() { compressedMoveEvent.reset(); moveEventCompressor.stop(); } bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event) { bool retval = false; /** * When Krita (as an application) has no input focus, we cannot * handle key events. But at the same time, when the user hovers * Krita canvas, we should still show him the correct cursor. * * So here we just add a simple workaround to resync shortcut * matcher's state at least against the basic modifiers, like * Shift, Control and Alt. */ QWidget *recievingWidget = dynamic_cast(eventsReceiver); if (recievingWidget && !recievingWidget->hasFocus()) { QVector guessedKeys; KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); guessedKeys << KisExtendedModifiersMapper::workaroundShiftAltMetaHell(&kevent); } matcher.recoveryModifiersWithoutFocus(guessedKeys); } if (!matcher.pointerMoved(event) && toolProxy) { toolProxy->forwardHoverEvent(event); } retval = true; event->setAccepted(true); return retval; } qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const { // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp, // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that // we compare against ourselves in QWindowSystemInterface. QElapsedTimer elapsed; elapsed.start(); return elapsed.msecsSinceReference(); } void KisInputManager::Private::TabletLatencyTracker::print(const QString &message) { dbgTablet << qUtf8Printable(message); } diff --git a/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h index b0ad408ec3..0cfdf3b90b 100644 --- a/libs/ui/input/kis_input_manager_p.h +++ b/libs/ui/input/kis_input_manager_p.h @@ -1,159 +1,164 @@ /* * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kis_input_manager.h" #include "kis_shortcut_matcher.h" #include "kis_shortcut_configuration.h" #include "kis_canvas2.h" #include "kis_tool_proxy.h" #include "kis_signal_compressor.h" #include "input/kis_tablet_debugger.h" #include "kis_timed_signal_threshold.h" #include "kis_signal_auto_connection.h" #include "kis_latency_tracker.h" class KisToolInvocationAction; class KisInputManager::Private { public: Private(KisInputManager *qq); bool tryHidePopupPalette(); void addStrokeShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, Qt::MouseButtons buttons); void addKeyShortcut(KisAbstractInputAction* action, int index,const QList &keys); void addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); bool addNativeGestureShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); void addWheelShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction); bool processUnhandledEvent(QEvent *event); void setupActions(); bool handleCompressedTabletEvent(QEvent *event); KisInputManager *q; QPointer canvas; QPointer toolProxy; bool forwardAllEventsToTool = false; bool ignoringQtCursorEvents(); bool touchHasBlockedPressEvents = false; KisShortcutMatcher matcher; KisToolInvocationAction *defaultInputAction = 0; QObject *eventsReceiver = 0; KisSignalCompressor moveEventCompressor; QScopedPointer compressedMoveEvent; bool testingAcceptCompressedTabletEvents = false; bool testingCompressBrushEvents = false; typedef QPair > PriorityPair; typedef QList PriorityList; PriorityList priorityEventFilter; int priorityEventFilterSeqNo; + bool touchStrokeStarted = false; + + QPointF previousPos; + bool buttonPressed = false; + void blockMouseEvents(); void allowMouseEvents(); void eatOneMousePress(); void setMaskSyntheticEvents(bool value); void resetCompressor(); template void debugEvent(QEvent *event) { if (!KisTabletDebugger::instance()->debugEnabled()) return; QString msg1 = useBlocking && ignoringQtCursorEvents() ? "[BLOCKED] " : "[ ]"; Event *specificEvent = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*specificEvent, msg1); } class ProximityNotifier : public QObject { public: ProximityNotifier(Private *_d, QObject *p); bool eventFilter(QObject* object, QEvent* event ) override; private: KisInputManager::Private *d; }; class CanvasSwitcher : public QObject { public: CanvasSwitcher(Private *_d, QObject *p); void addCanvas(KisCanvas2 *canvas); void removeCanvas(KisCanvas2 *canvas); bool eventFilter(QObject* object, QEvent* event ) override; private: void setupFocusThreshold(QObject *object); private: KisInputManager::Private *d; QMap> canvasResolver; int eatOneMouseStroke; KisTimedSignalThreshold focusSwitchThreshold; KisSignalAutoConnectionsStore thresholdConnections; }; CanvasSwitcher canvasSwitcher; struct EventEater { EventEater(); bool eventFilter(QObject* target, QEvent* event); // This should be called after we're certain a tablet stroke has started. void activate(); // This should be called after a tablet stroke has ended. void deactivate(); // On Windows, we sometimes receive mouse events very late, so watch & wait. void eatOneMousePress(); bool hungry{false}; // Continue eating mouse strokes bool peckish{false}; // Eat a single mouse press event bool eatSyntheticEvents{false}; // Mask all synthetic events bool activateSecondaryButtonsWorkaround{false}; // Use mouse events for right- and middle-clicks }; EventEater eventEater; bool containsPointer = false; int accumulatedScrollDelta = 0; class TabletLatencyTracker : public KisLatencyTracker { protected: virtual qint64 currentTimestamp() const; virtual void print(const QString &message); }; KisSharedPtr tabletLatencyTracker; }; diff --git a/libs/ui/input/kis_pan_action.cpp b/libs/ui/input/kis_pan_action.cpp index 03565f227e..18eec430ff 100644 --- a/libs/ui/input/kis_pan_action.cpp +++ b/libs/ui/input/kis_pan_action.cpp @@ -1,198 +1,205 @@ /* 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_pan_action.h" #include #include #include #include #include #include #include #include "kis_input_manager.h" class KisPanAction::Private { public: Private() : panDistance(10) { } QPointF averagePoint( QTouchEvent* event ); const int panDistance; QPointF lastPosition; + QPointF originalPreferredCenter; }; KisPanAction::KisPanAction() : KisAbstractInputAction("Pan Canvas") , d(new Private) { setName(i18n("Pan Canvas")); setDescription(i18n("The Pan Canvas action pans the canvas.")); QHash shortcuts; shortcuts.insert(i18n("Pan Mode"), PanModeShortcut); shortcuts.insert(i18n("Pan Left"), PanLeftShortcut); shortcuts.insert(i18n("Pan Right"), PanRightShortcut); shortcuts.insert(i18n("Pan Up"), PanUpShortcut); shortcuts.insert(i18n("Pan Down"), PanDownShortcut); setShortcutIndexes(shortcuts); } KisPanAction::~KisPanAction() { delete d; } int KisPanAction::priority() const { return 5; } void KisPanAction::activate(int shortcut) { Q_UNUSED(shortcut); QApplication::setOverrideCursor(Qt::OpenHandCursor); } void KisPanAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisPanAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); bool overrideCursor = true; switch (shortcut) { case PanModeShortcut: { QTouchEvent *tevent = dynamic_cast(event); if (tevent) { d->lastPosition = d->averagePoint(tevent); break; } // Some QT wheel events are actually be touch pad pan events. From the QT docs: // "Wheel events are generated for both mouse wheels and trackpad scroll gestures." QWheelEvent *wheelEvent = dynamic_cast(event); if (wheelEvent) { inputManager()->canvas()->canvasController()->pan(-wheelEvent->pixelDelta()); overrideCursor = false; break; } + d->originalPreferredCenter = inputManager()->canvas()->canvasController()->preferredCenter(); + break; } case PanLeftShortcut: inputManager()->canvas()->canvasController()->pan(QPoint(d->panDistance, 0)); break; case PanRightShortcut: inputManager()->canvas()->canvasController()->pan(QPoint(-d->panDistance, 0)); break; case PanUpShortcut: inputManager()->canvas()->canvasController()->pan(QPoint(0, d->panDistance)); break; case PanDownShortcut: inputManager()->canvas()->canvasController()->pan(QPoint(0, -d->panDistance)); break; } if (overrideCursor) { QApplication::setOverrideCursor(Qt::ClosedHandCursor); } } void KisPanAction::end(QEvent *event) { QApplication::restoreOverrideCursor(); KisAbstractInputAction::end(event); } void KisPanAction::inputEvent(QEvent *event) { switch (event->type()) { case QEvent::Gesture: { QGestureEvent *gevent = static_cast(event); if (gevent->activeGestures().at(0)->gestureType() == Qt::PanGesture) { QPanGesture *pan = static_cast(gevent->activeGestures().at(0)); inputManager()->canvas()->canvasController()->pan(-pan->delta().toPoint() * 0.2); } return; } case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); QPointF newPos = d->averagePoint(tevent); QPointF delta = newPos - d->lastPosition; // If this is enormously large, then we are likely in the process of ending the gesture, // with fingers being lifted one by one from the perspective of our very speedy operations, // and as such, ignore those big jumps. if(delta.manhattanLength() < 50) { inputManager()->canvas()->canvasController()->pan(-delta.toPoint()); d->lastPosition = newPos; } return; } default: break; } KisAbstractInputAction::inputEvent(event); } -void KisPanAction::cursorMoved(const QPointF &lastPos, const QPointF &pos) +void KisPanAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) { - QPointF relMovement = -(pos - lastPos); - inputManager()->canvas()->canvasController()->pan(relMovement.toPoint()); + inputManager()->canvas()->canvasController()->setPreferredCenter(-pos + startPos + d->originalPreferredCenter); } QPointF KisPanAction::Private::averagePoint( QTouchEvent* event ) { QPointF result; int count = 0; Q_FOREACH ( QTouchEvent::TouchPoint point, event->touchPoints() ) { if( point.state() != Qt::TouchPointReleased ) { result += point.screenPos(); count++; } } if( count > 0 ) { return result / count; } else { return QPointF(); } } bool KisPanAction::isShortcutRequired(int shortcut) const { return shortcut == PanModeShortcut; } KisInputActionGroup KisPanAction::inputActionGroup(int shortcut) const { Q_UNUSED(shortcut); return ViewTransformActionGroup; } + +bool KisPanAction::supportsHiResInputEvents() const +{ + return true; +} diff --git a/libs/ui/input/kis_pan_action.h b/libs/ui/input/kis_pan_action.h index 098d560cc7..20d251167b 100644 --- a/libs/ui/input/kis_pan_action.h +++ b/libs/ui/input/kis_pan_action.h @@ -1,66 +1,67 @@ /* 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. */ #ifndef KIS_PAN_ACTION_H #define KIS_PAN_ACTION_H #include "kis_abstract_input_action.h" /** * \brief Pan Canvas implementation of KisAbstractInputAction. * * The Pan Canvas action pans the canvas. */ class KisPanAction : public KisAbstractInputAction { public: /** * The different behaviours for this action. */ enum Shortcut { PanModeShortcut, ///< Toggle the pan mode. PanLeftShortcut, ///< Pan left by a fixed amount. PanRightShortcut, ///< Pan right by a fixed amount. PanUpShortcut, ///< Pan up by a fixed amount. PanDownShortcut ///< Pan down by a fixed amount. }; explicit KisPanAction(); ~KisPanAction() override; int priority() const override; void activate(int shortcut) override; void deactivate(int shortcut) override; void begin(int shortcut, QEvent *event) override; void end(QEvent *event) override; void inputEvent(QEvent* event) override; - void cursorMoved(const QPointF &lastPos, const QPointF &pos) override; + void cursorMovedAbsolute(const QPointF &lastPos, const QPointF &pos) override; bool isShortcutRequired(int shortcut) const override; KisInputActionGroup inputActionGroup(int shortcut) const override; + bool supportsHiResInputEvents() const; private: class Private; Private * const d; }; #endif // KIS_PAN_ACTION_H diff --git a/libs/ui/input/kis_rotate_canvas_action.cpp b/libs/ui/input/kis_rotate_canvas_action.cpp index 4a4ae0a60e..0f4c29fc8a 100644 --- a/libs/ui/input/kis_rotate_canvas_action.cpp +++ b/libs/ui/input/kis_rotate_canvas_action.cpp @@ -1,158 +1,192 @@ /* 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_rotate_canvas_action.h" #include #include #include #include "kis_cursor.h" #include "kis_canvas_controller.h" #include #include "kis_input_manager.h" #include class KisRotateCanvasAction::Private { public: - Private() : angleDrift(0) {} + Private() : previousAngle(0) {} Shortcut mode; - qreal angleDrift; + + qreal previousAngle; + qreal startRotation; + qreal previousRotation; }; KisRotateCanvasAction::KisRotateCanvasAction() : KisAbstractInputAction("Rotate Canvas") , d(new Private()) { setName(i18n("Rotate Canvas")); setDescription(i18n("The Rotate Canvas action rotates the canvas.")); QHash shortcuts; shortcuts.insert(i18n("Rotate Mode"), RotateModeShortcut); shortcuts.insert(i18n("Discrete Rotate Mode"), DiscreteRotateModeShortcut); shortcuts.insert(i18n("Rotate Left"), RotateLeftShortcut); shortcuts.insert(i18n("Rotate Right"), RotateRightShortcut); shortcuts.insert(i18n("Reset Rotation"), RotateResetShortcut); setShortcutIndexes(shortcuts); } KisRotateCanvasAction::~KisRotateCanvasAction() { delete d; } int KisRotateCanvasAction::priority() const { return 3; } void KisRotateCanvasAction::activate(int shortcut) { if (shortcut == DiscreteRotateModeShortcut) { QApplication::setOverrideCursor(KisCursor::rotateCanvasDiscreteCursor()); } else /* if (shortcut == SmoothRotateModeShortcut) */ { QApplication::setOverrideCursor(KisCursor::rotateCanvasSmoothCursor()); } } void KisRotateCanvasAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisRotateCanvasAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); + d->previousAngle = 0; KisCanvasController *canvasController = dynamic_cast(inputManager()->canvas()->canvasController()); switch(shortcut) { case RotateModeShortcut: - d->mode = (Shortcut)shortcut; - break; case DiscreteRotateModeShortcut: d->mode = (Shortcut)shortcut; - d->angleDrift = 0; + d->startRotation = inputManager()->canvas()->rotationAngle(); + d->previousRotation = 0; break; case RotateLeftShortcut: canvasController->rotateCanvasLeft15(); break; case RotateRightShortcut: canvasController->rotateCanvasRight15(); break; case RotateResetShortcut: canvasController->resetCanvasRotation(); break; } } -void KisRotateCanvasAction::cursorMoved(const QPointF &lastPos, const QPointF &pos) +void KisRotateCanvasAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) { const KisCoordinatesConverter *converter = inputManager()->canvas()->coordinatesConverter(); - QPointF centerPoint = converter->flakeToWidget(converter->flakeCenterPoint()); - QPointF oldPoint = lastPos - centerPoint; - QPointF newPoint = pos - centerPoint; + const QPointF centerPoint = converter->flakeToWidget(converter->flakeCenterPoint()); + const QPointF startPoint = startPos - centerPoint; + const QPointF newPoint = pos - centerPoint; - qreal oldAngle = atan2(oldPoint.y(), oldPoint.x()); - qreal newAngle = atan2(newPoint.y(), newPoint.x()); + const qreal oldAngle = atan2(startPoint.y(), startPoint.x()); + const qreal newAngle = atan2(newPoint.y(), newPoint.x()); - qreal angle = (180 / M_PI) * (newAngle - oldAngle); + qreal newRotation = (180 / M_PI) * (newAngle - oldAngle); if (d->mode == DiscreteRotateModeShortcut) { const qreal angleStep = 15; - qreal initialAngle = inputManager()->canvas()->rotationAngle(); - qreal roundedAngle = qRound((initialAngle + angle + d->angleDrift) / angleStep) * angleStep - initialAngle; - d->angleDrift += angle - roundedAngle; - angle = roundedAngle; + newRotation = qRound(newRotation / angleStep) * angleStep; } KisCanvasController *canvasController = dynamic_cast(inputManager()->canvas()->canvasController()); - canvasController->rotateCanvas(angle); + canvasController->rotateCanvas(newRotation - d->previousRotation); + d->previousRotation = newRotation; } void KisRotateCanvasAction::inputEvent(QEvent* event) { switch (event->type()) { case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); KisCanvas2 *canvas = inputManager()->canvas(); KisCanvasController *controller = static_cast(canvas->canvasController()); const float angle = gevent->value(); QPoint widgetPos = canvas->canvasWidget()->mapFromGlobal(gevent->globalPos()); controller->rotateCanvas(angle, widgetPos); return; } + case QEvent::TouchUpdate: { + QTouchEvent *touchEvent = static_cast(event); + + if (touchEvent->touchPoints().count() != 2) + break; + + QPointF p0 = touchEvent->touchPoints().at(0).pos(); + QPointF p1 = touchEvent->touchPoints().at(1).pos(); + + // high school (y2 - y1) / (x2 - x1) + QPointF slope = p1 - p0; + qreal newAngle = atan2(slope.y(), slope.x()); + + if (!d->previousAngle) + { + d->previousAngle = newAngle; + return; + } + + qreal delta = (180 / M_PI) * (newAngle - d->previousAngle); + + KisCanvas2 *canvas = inputManager()->canvas(); + KisCanvasController *controller = static_cast(canvas->canvasController()); + controller->rotateCanvas(delta); + + d->previousAngle = newAngle; + return; + } default: break; } KisAbstractInputAction::inputEvent(event); } KisInputActionGroup KisRotateCanvasAction::inputActionGroup(int shortcut) const { Q_UNUSED(shortcut); return ViewTransformActionGroup; } + +bool KisRotateCanvasAction::supportsHiResInputEvents() const +{ + return true; +} diff --git a/libs/ui/input/kis_rotate_canvas_action.h b/libs/ui/input/kis_rotate_canvas_action.h index 7cee3b3672..d3e949b17b 100644 --- a/libs/ui/input/kis_rotate_canvas_action.h +++ b/libs/ui/input/kis_rotate_canvas_action.h @@ -1,62 +1,63 @@ /* 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. */ #ifndef KIS_ROTATE_CANVAS_ACTION_H #define KIS_ROTATE_CANVAS_ACTION_H #include "kis_abstract_input_action.h" /** * \brief Rotate Canvas implementation of KisAbstractInputAction. * * The Rotate Canvas action rotates the canvas. */ class KisRotateCanvasAction : public KisAbstractInputAction { public: /** * The different behaviours for this action. */ enum Shortcut { RotateModeShortcut, ///< Toggle Rotate mode. DiscreteRotateModeShortcut, ///< Toggle Discrete Rotate mode. RotateLeftShortcut, ///< Rotate left by a fixed amount. RotateRightShortcut, ///< Rotate right by a fixed amount. RotateResetShortcut ///< Reset the rotation to 0. }; explicit KisRotateCanvasAction(); ~KisRotateCanvasAction() override; int priority() const override; void activate(int shortcut) override; void deactivate(int shortcut) override; void begin(int shortcut, QEvent *event) override; - void cursorMoved(const QPointF &lastPos, const QPointF &pos) override; + void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos); void inputEvent(QEvent* event) override; KisInputActionGroup inputActionGroup(int shortcut) const override; + bool supportsHiResInputEvents() const; private: class Private; Private * const d; }; #endif // KIS_ROTATE_CANVAS_ACTION_H diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp index 352bb2f653..183149d8e7 100644 --- a/libs/ui/input/kis_shortcut_matcher.cpp +++ b/libs/ui/input/kis_shortcut_matcher.cpp @@ -1,687 +1,690 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shortcut_matcher.h" #include #include #include #include "kis_assert.h" #include "kis_abstract_input_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" +#include "kis_config.h" #ifdef DEBUG_MATCHER #include #define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text; #define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name(); #define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys; #define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys; #define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();} #else #define DEBUG_ACTION(text) #define DEBUG_KEY(text) #define DEBUG_SHORTCUT(text, shortcut) #define DEBUG_BUTTON_ACTION(text, button) #define DEBUG_EVENT_ACTION(text, event) #endif class Q_DECL_HIDDEN KisShortcutMatcher::Private { public: Private() : runningShortcut(0) , readyShortcut(0) , touchShortcut(0) , nativeGestureShortcut(0) , actionGroupMask([] () { return AllActionGroup; }) , suppressAllActions(false) , cursorEntered(false) , usingTouch(false) , usingNativeGesture(false) {} ~Private() { qDeleteAll(singleActionShortcuts); qDeleteAll(strokeShortcuts); qDeleteAll(touchShortcuts); } QList singleActionShortcuts; QList strokeShortcuts; QList touchShortcuts; QList nativeGestureShortcuts; QSet keys; // Model of currently pressed keys QSet buttons; // Model of currently pressed buttons KisStrokeShortcut *runningShortcut; KisStrokeShortcut *readyShortcut; QList candidateShortcuts; KisTouchShortcut *touchShortcut; KisNativeGestureShortcut *nativeGestureShortcut; std::function actionGroupMask; bool suppressAllActions; bool cursorEntered; bool usingTouch; bool usingNativeGesture; inline bool actionsSuppressed() const { - return suppressAllActions || !cursorEntered; + return (suppressAllActions || !cursorEntered) + && KisConfig(true).disableTouchOnCanvas(); } inline bool actionsSuppressedIgnoreFocus() const { return suppressAllActions; } + // only for touch events with touchPoints count >= 2 inline bool isUsingTouch() const { return usingTouch || usingNativeGesture; } }; KisShortcutMatcher::KisShortcutMatcher() : m_d(new Private) {} KisShortcutMatcher::~KisShortcutMatcher() { delete m_d; } bool KisShortcutMatcher::hasRunningShortcut() const { return m_d->runningShortcut; } void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut) { m_d->singleActionShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut) { m_d->strokeShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut ) { m_d->touchShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) { m_d->nativeGestureShortcuts.append(shortcut); } bool KisShortcutMatcher::supportsHiResInputEvents() { return m_d->runningShortcut && m_d->runningShortcut->action() && m_d->runningShortcut->action()->supportsHiResInputEvents(); } bool KisShortcutMatcher::keyPressed(Qt::Key key) { bool retval = false; if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); } if (!m_d->runningShortcut) { retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys); } m_d->keys.insert(key); DEBUG_KEY("Pressed"); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key) { bool retval = false; if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); } if (!m_d->runningShortcut) { // Autorepeated key should not be included in the shortcut QSet filteredKeys = m_d->keys; filteredKeys.remove(key); retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys); } return retval; } bool KisShortcutMatcher::keyReleased(Qt::Key key) { if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed"); else m_d->keys.remove(key); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return false; } bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event) { DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); } - if (!m_d->runningShortcut) { + if (!hasRunningShortcut()) { prepareReadyShortcuts(); retval = tryRunReadyShortcut(button, event); } m_d->buttons.insert(button); - if (!m_d->runningShortcut) { + if (!hasRunningShortcut()) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event) { DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->runningShortcut && !m_d->readyShortcut) { retval = tryEndRunningShortcut(button, event); DEBUG_BUTTON_ACTION("ended", button); } if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed"); else m_d->buttons.remove(button); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { if (m_d->runningShortcut || m_d->isUsingTouch()) { DEBUG_ACTION("Wheel event canceled."); return false; } return tryRunWheelShortcut(wheelAction, event); } bool KisShortcutMatcher::pointerMoved(QEvent *event) { if (m_d->isUsingTouch() || !m_d->runningShortcut) { return false; } m_d->runningShortcut->action()->inputEvent(event); return true; } void KisShortcutMatcher::enterEvent() { m_d->cursorEntered = true; if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::leaveEvent() { m_d->cursorEntered = false; if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event ) { Q_UNUSED(event) return true; } bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event ) { bool retval = false; if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) { retval = tryEndTouchShortcut( event ); } if (!m_d->touchShortcut ) { retval = tryRunTouchShortcut( event ); } else { m_d->touchShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event ) { m_d->usingTouch = false; // we need to say we are done because qt will not send further event // we should try and end the shortcut too (it might be that there is none? (sketch)) if (tryEndTouchShortcut(event)) { return true; } return false; } bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event) { Q_UNUSED(event) return true; } bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event) { bool retval = false; if ( !m_d->nativeGestureShortcut ) { retval = tryRunNativeGestureShortcut( event ); } else { m_d->nativeGestureShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event) { if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) { tryEndNativeGestureShortcut( event ); } m_d->usingNativeGesture = false; return true; } Qt::MouseButtons listToFlags(const QList &list) { Qt::MouseButtons flags; Q_FOREACH (Qt::MouseButton b, list) { flags |= b; } return flags; } void KisShortcutMatcher::reinitialize() { reset("reinitialize"); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector &keys) { Q_FOREACH (Qt::Key key, m_d->keys) { if (!keys.contains(key)) { keyReleased(key); } } Q_FOREACH (Qt::Key key, keys) { if (!m_d->keys.contains(key)) { keyPressed(key); } } if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } DEBUG_ACTION("recoverySyncModifiers"); } void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos) { if (m_d->runningShortcut) { forceEndRunningShortcut(localPos); } } void KisShortcutMatcher::reset() { m_d->keys.clear(); m_d->buttons.clear(); DEBUG_ACTION("reset!"); } void KisShortcutMatcher::reset(QString msg) { m_d->keys.clear(); m_d->buttons.clear(); Q_UNUSED(msg); DEBUG_ACTION(msg); } void KisShortcutMatcher::suppressAllActions(bool value) { m_d->suppressAllActions = value; } void KisShortcutMatcher::clearShortcuts() { reset("Clearing shortcuts"); qDeleteAll(m_d->singleActionShortcuts); m_d->singleActionShortcuts.clear(); qDeleteAll(m_d->strokeShortcuts); qDeleteAll(m_d->touchShortcuts); m_d->strokeShortcuts.clear(); m_d->candidateShortcuts.clear(); m_d->touchShortcuts.clear(); m_d->runningShortcut = 0; m_d->readyShortcut = 0; } void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function func) { m_d->actionGroupMask = func; } bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys); } // Note: sometimes event can be zero!! template bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState) { if (m_d->actionsSuppressedIgnoreFocus()) { DEBUG_EVENT_ACTION("Event suppressed", event) return false; } KisSingleActionShortcut *goodCandidate = 0; Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) { if(s->isAvailable(m_d->actionGroupMask()) && s->match(keysState, param) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { DEBUG_EVENT_ACTION("Beginning action for event", event) goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); goodCandidate->action()->end(0); } else { DEBUG_EVENT_ACTION("Could not match a candidate for event", event) } return goodCandidate; } void KisShortcutMatcher::prepareReadyShortcuts() { m_d->candidateShortcuts.clear(); if (m_d->actionsSuppressed()) return; Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) { if (s->matchReady(m_d->keys, m_d->buttons)) { m_d->candidateShortcuts.append(s); } } } bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ) { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (s->isAvailable(m_d->actionGroupMask()) && s->matchBegin(button) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut) { if (m_d->readyShortcut != goodCandidate) { m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } m_d->readyShortcut = 0; } else { DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } DEBUG_SHORTCUT("Starting new action", goodCandidate); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->runningShortcut = goodCandidate; } return goodCandidate; } void KisShortcutMatcher::tryActivateReadyShortcut() { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (!goodCandidate || s->priority() > goodCandidate->priority()) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) { DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } if (!m_d->readyShortcut) { DEBUG_SHORTCUT("Preparing new ready action", goodCandidate); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); m_d->readyShortcut = goodCandidate; } } else if (m_d->readyShortcut) { DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } } bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ) { Q_ASSERT(m_d->runningShortcut); Q_ASSERT(!m_d->readyShortcut); if (m_d->runningShortcut->matchBegin(button)) { // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_EVENT_ACTION("Ending running shortcut at event", event); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); action->end(event); action->deactivate(shortcutIndex); } } return !m_d->runningShortcut; } void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut); KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->readyShortcut); // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_ACTION("Forced ending running shortcut at event"); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); QMouseEvent event = runningShortcut->fakeEndEvent(localPos); action->end(&event); action->deactivate(shortcutIndex); } } bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event ) { KisTouchShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) { if (shortcut->isAvailable(m_d->actionGroupMask()) && shortcut->match( event ) && (!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) { goodCandidate = shortcut; } } if( goodCandidate ) { if( m_d->runningShortcut ) { - QMouseEvent mouseEvent(QEvent::MouseButtonRelease, - event->touchPoints().at(0).pos().toPoint(), - Qt::LeftButton, - Qt::LeftButton, - event->modifiers()); - tryEndRunningShortcut(Qt::LeftButton, &mouseEvent); + QTouchEvent touchEvent(QEvent::TouchEnd, + event->device(), + event->modifiers(), + Qt::TouchPointReleased, + event->touchPoints()); + tryEndRunningShortcut(Qt::LeftButton, &touchEvent); } goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->touchShortcut = goodCandidate; m_d->usingTouch = true; } return goodCandidate; } bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event ) { if(m_d->touchShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisTouchShortcut *touchShortcut = m_d->touchShortcut; touchShortcut->action()->end(event); touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex()); m_d->touchShortcut = 0; // empty it out now that we are done with it return true; } return false; } bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event) { KisNativeGestureShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) { if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) { goodCandidate = shortcut; } } if (goodCandidate) { goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->nativeGestureShortcut = goodCandidate; m_d->usingNativeGesture = true; return true; } return false; } bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event) { if (m_d->nativeGestureShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut; nativeGestureShortcut->action()->end(event); nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex()); m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it return true; } return false; } diff --git a/libs/ui/input/kis_touch_shortcut.h b/libs/ui/input/kis_touch_shortcut.h index d2b59d3b5e..b0ca049d5a 100644 --- a/libs/ui/input/kis_touch_shortcut.h +++ b/libs/ui/input/kis_touch_shortcut.h @@ -1,45 +1,50 @@ /* * 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. * */ #ifndef KISTOUCHSHORTCUT_H #define KISTOUCHSHORTCUT_H #include "kis_abstract_shortcut.h" class QTouchEvent; +/** + * @brief The KisTouchShortcut class only handles touch gestures + * it _does not_ handle tool invocation i.e painting (which is being + * handled in KisShortcutMatcher). + */ class KisTouchShortcut : public KisAbstractShortcut { public: KisTouchShortcut( KisAbstractInputAction* action, int index ); ~KisTouchShortcut() override; int priority() const override; void setMinimumTouchPoints( int min ); void setMaximumTouchPoints( int max ); bool match( QTouchEvent* event ); private: class Private; Private * const d; }; #endif // KISTOUCHSHORTCUT_H diff --git a/libs/ui/input/kis_zoom_action.cpp b/libs/ui/input/kis_zoom_action.cpp index 3f65621538..a35fb1cc02 100644 --- a/libs/ui/input/kis_zoom_action.cpp +++ b/libs/ui/input/kis_zoom_action.cpp @@ -1,310 +1,321 @@ /* 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_zoom_action.h" #include #include #include #include #include #include #include #include "kis_cursor.h" #include "KisViewManager.h" #include "kis_input_manager.h" #include "kis_config.h" inline QPoint pointFromEvent(QEvent *event) { if (!event) { return QPoint(); } else if (QMouseEvent *mouseEvent = dynamic_cast(event)) { return mouseEvent->pos(); } else if (QTabletEvent *tabletEvent = dynamic_cast(event)) { return tabletEvent->pos(); } else if (QWheelEvent *wheelEvent = dynamic_cast(event)) { return wheelEvent->pos(); } return QPoint(); } class KisZoomAction::Private { public: - Private(KisZoomAction *qq) : q(qq), distance(0), lastDistance(0.f) {} + Private(KisZoomAction *qq) : q(qq), lastDistance(0.f) {} QPointF centerPoint(QTouchEvent* event); KisZoomAction *q; - int distance; Shortcuts mode; QPointF lastPosition; float lastDistance; - QPoint startPoint; + qreal startZoom = 1.0; + qreal lastDescreteZoomDistance = 0.0; void zoomTo(bool zoomIn, const QPoint &pos); }; QPointF KisZoomAction::Private::centerPoint(QTouchEvent* event) { QPointF result; int count = 0; Q_FOREACH (QTouchEvent::TouchPoint point, event->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { result += point.pos(); count++; } } if (count > 0) { return result / count; } else { return QPointF(); } } void KisZoomAction::Private::zoomTo(bool zoomIn, const QPoint &point) { KoZoomAction *zoomAction = q->inputManager()->canvas()->viewManager()->zoomController()->zoomAction(); if (!point.isNull()) { float oldZoom = zoomAction->effectiveZoom(); float newZoom = zoomIn ? zoomAction->nextZoomLevel() : zoomAction->prevZoomLevel(); KoCanvasControllerWidget *controller = dynamic_cast( q->inputManager()->canvas()->canvasController()); controller->zoomRelativeToPoint(point, newZoom / oldZoom); } else { if (zoomIn) { zoomAction->zoomIn(); } else { zoomAction->zoomOut(); } } } KisZoomAction::KisZoomAction() : KisAbstractInputAction("Zoom Canvas") , d(new Private(this)) { setName(i18n("Zoom Canvas")); setDescription(i18n("The Zoom Canvas action zooms the canvas.")); QHash< QString, int > shortcuts; shortcuts.insert(i18n("Zoom Mode"), ZoomModeShortcut); shortcuts.insert(i18n("Discrete Zoom Mode"), DiscreteZoomModeShortcut); shortcuts.insert(i18n("Relative Zoom Mode"), RelativeZoomModeShortcut); shortcuts.insert(i18n("Relative Discrete Zoom Mode"), RelativeDiscreteZoomModeShortcut); shortcuts.insert(i18n("Zoom In"), ZoomInShortcut); shortcuts.insert(i18n("Zoom Out"), ZoomOutShortcut); shortcuts.insert(i18n("Reset Zoom to 100%"), ZoomResetShortcut); shortcuts.insert(i18n("Fit to Page"), ZoomToPageShortcut); shortcuts.insert(i18n("Fit to Width"), ZoomToWidthShortcut); setShortcutIndexes(shortcuts); } KisZoomAction::~KisZoomAction() { delete d; } int KisZoomAction::priority() const { return 4; } void KisZoomAction::activate(int shortcut) { if (shortcut == DiscreteZoomModeShortcut || shortcut == RelativeDiscreteZoomModeShortcut) { QApplication::setOverrideCursor(KisCursor::zoomDiscreteCursor()); } else /* if (shortcut == SmoothZoomModeShortcut) */ { QApplication::setOverrideCursor(KisCursor::zoomSmoothCursor()); } } void KisZoomAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisZoomAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); d->lastDistance = 0.f; switch(shortcut) { case ZoomModeShortcut: case RelativeZoomModeShortcut: { - d->startPoint = pointFromEvent(event); + d->startZoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); d->mode = (Shortcuts)shortcut; QTouchEvent *tevent = dynamic_cast(event); if(tevent) d->lastPosition = d->centerPoint(tevent); break; } case DiscreteZoomModeShortcut: case RelativeDiscreteZoomModeShortcut: - d->startPoint = pointFromEvent(event); + d->startZoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); + d->lastDescreteZoomDistance = 0; d->mode = (Shortcuts)shortcut; - d->distance = 0; break; case ZoomInShortcut: d->zoomTo(true, pointFromEvent(event)); break; case ZoomOutShortcut: d->zoomTo(false, pointFromEvent(event)); break; case ZoomResetShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); break; case ZoomToPageShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 1.0); break; case ZoomToWidthShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0); break; } } void KisZoomAction::inputEvent( QEvent* event ) { switch (event->type()) { case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); QPointF center = d->centerPoint(tevent); int count = 0; float dist = 0.0f; Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; dist += (point.pos() - center).manhattanLength(); } } dist /= count; float delta = qFuzzyCompare(1.0f, 1.0f + d->lastDistance) ? 1.f : dist / d->lastDistance; if(qAbs(delta) > 0.1f) { qreal zoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); Q_UNUSED(zoom); static_cast(inputManager()->canvas()->canvasController())->zoomRelativeToPoint(center.toPoint(), delta); d->lastDistance = dist; // Also do panning here, as doing it later requires a further check for validity QPointF moveDelta = center - d->lastPosition; inputManager()->canvas()->canvasController()->pan(-moveDelta.toPoint()); d->lastPosition = center; } return; // Don't try to update the cursor during a pinch-zoom } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); if (gevent->gestureType() == Qt::ZoomNativeGesture) { KisCanvas2 *canvas = inputManager()->canvas(); KisCanvasController *controller = static_cast(canvas->canvasController()); const float delta = 1.0f + gevent->value(); controller->zoomRelativeToPoint(canvas->canvasWidget()->mapFromGlobal(gevent->globalPos()), delta); } else if (gevent->gestureType() == Qt::SmartZoomNativeGesture) { KisCanvas2 *canvas = inputManager()->canvas(); KoZoomController *controller = canvas->viewManager()->zoomController(); if (controller->zoomMode() != KoZoomMode::ZOOM_WIDTH) { controller->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0); } else { controller->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); } } return; } default: break; } KisAbstractInputAction::inputEvent(event); } -void KisZoomAction::cursorMoved(const QPointF &lastPos, const QPointF &pos) +void KisZoomAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) { - QPointF diff = -(pos - lastPos); + QPointF diff = -(pos - startPos); const int stepCont = 100; - const int stepDisc = 20; + const int stepDisc = 50; if (d->mode == ZoomModeShortcut || d->mode == RelativeZoomModeShortcut) { + + const qreal zoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); + const qreal logDistance = std::pow(2.0, qreal(diff.y()) / qreal(stepCont)); + KisConfig cfg(true); - float coeff; + qreal newZoom = zoom; if (cfg.readEntry("InvertMiddleClickZoom", false)) { - coeff = 1.0 - qreal(diff.y()) / stepCont; - } - else { - coeff = 1.0 + qreal(diff.y()) / stepCont; + newZoom = d->startZoom / logDistance; + } else { + newZoom = d->startZoom * logDistance; } if (d->mode == ZoomModeShortcut) { - float zoom = coeff * inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); - inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom); + inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom); } else { + const qreal coeff = newZoom / zoom; + KoCanvasControllerWidget *controller = dynamic_cast( inputManager()->canvas()->canvasController()); - controller->zoomRelativeToPoint(d->startPoint, coeff); + controller->zoomRelativeToPoint(startPos.toPoint(), coeff); } } else if (d->mode == DiscreteZoomModeShortcut || d->mode == RelativeDiscreteZoomModeShortcut) { - d->distance += diff.y(); QPoint stillPoint = d->mode == RelativeDiscreteZoomModeShortcut ? - d->startPoint : QPoint(); + startPos.toPoint() : QPoint(); - bool zoomIn = d->distance > 0; - while (qAbs(d->distance) > stepDisc) { + qreal currentDiff = qreal(diff.y()) / stepDisc - d->lastDescreteZoomDistance; + + bool zoomIn = currentDiff > 0; + while (qAbs(currentDiff) > 1.0) { d->zoomTo(zoomIn, stillPoint); - d->distance += zoomIn ? -stepDisc : stepDisc; + d->lastDescreteZoomDistance += zoomIn ? 1.0 : -1.0; + currentDiff = qreal(diff.y()) / stepDisc - d->lastDescreteZoomDistance; } } } bool KisZoomAction::isShortcutRequired(int shortcut) const { return shortcut == ZoomModeShortcut; } +bool KisZoomAction::supportsHiResInputEvents() const +{ + return true; +} + KisInputActionGroup KisZoomAction::inputActionGroup(int shortcut) const { Q_UNUSED(shortcut); return ViewTransformActionGroup; } diff --git a/libs/ui/input/kis_zoom_action.h b/libs/ui/input/kis_zoom_action.h index 70ac4b6e18..78de9831bb 100644 --- a/libs/ui/input/kis_zoom_action.h +++ b/libs/ui/input/kis_zoom_action.h @@ -1,67 +1,68 @@ /* 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. */ #ifndef KIS_ZOOM_ACTION_H #define KIS_ZOOM_ACTION_H #include "kis_abstract_input_action.h" /** * \brief Zoom Canvas implementation of KisAbstractInputAction. * * The Zoom Canvas action zooms the canvas. */ class KisZoomAction : public KisAbstractInputAction { public: /** * The different behaviours for this action. */ enum Shortcuts { ZoomModeShortcut, ///< Toggle zoom mode. DiscreteZoomModeShortcut, ///< Toggle discrete zoom mode ZoomInShortcut, ///< Zoom in by a fixed amount. ZoomOutShortcut, ///< Zoom out by a fixed amount. ZoomResetShortcut, ///< Reset zoom to 100%. ZoomToPageShortcut, ///< Zoom fit to page. ZoomToWidthShortcut, ///< Zoom fit to width. RelativeZoomModeShortcut, ///< Toggle zoom mode relative to cursor RelativeDiscreteZoomModeShortcut ///< Toggle discrete zoom mode relative to cursor }; explicit KisZoomAction(); ~KisZoomAction() override; int priority() const override; void activate(int shortcut) override; void deactivate(int shortcut) override; void begin(int shortcut, QEvent *event = 0) override; void inputEvent(QEvent* event) override; - void cursorMoved(const QPointF &lastPos, const QPointF &pos) override; + void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) override; bool isShortcutRequired(int shortcut) const override; + bool supportsHiResInputEvents() const override; KisInputActionGroup inputActionGroup(int shortcut) const override; private: class Private; Private * const d; }; #endif // KIS_ZOOM_ACTION_H diff --git a/libs/ui/input/kis_zoom_and_rotate_action.cpp b/libs/ui/input/kis_zoom_and_rotate_action.cpp new file mode 100644 index 0000000000..c7f0be315a --- /dev/null +++ b/libs/ui/input/kis_zoom_and_rotate_action.cpp @@ -0,0 +1,79 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 Sharaf Zaman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "kis_zoom_and_rotate_action.h" +#include "kis_zoom_action.h" +#include "kis_rotate_canvas_action.h" + +class KisZoomAndRotateAction::Private { +public: + Private(): zoomAction(new KisZoomAction), rotateAction(new KisRotateCanvasAction) {} + KisZoomAction *zoomAction; + KisRotateCanvasAction *rotateAction; +}; + +KisZoomAndRotateAction::KisZoomAndRotateAction() + : KisAbstractInputAction ("Zoom and Rotate Canvas") + , d(new Private) +{ + setName(i18n("Zoom and Rotate Canvas")); +} + +int KisZoomAndRotateAction::priority() const +{ + return 5; +} + +void KisZoomAndRotateAction::activate(int shortcut) +{ + d->zoomAction->activate(shortcut); + d->rotateAction->activate(shortcut); +} + +void KisZoomAndRotateAction::deactivate(int shortcut) +{ + d->zoomAction->deactivate(shortcut); + d->rotateAction->deactivate(shortcut); +} + +void KisZoomAndRotateAction::begin(int shortcut, QEvent *event) +{ + d->zoomAction->begin(shortcut, event); + d->rotateAction->begin(shortcut, event); +} + +void KisZoomAndRotateAction::cursorMovedAbsolute(const QPointF &lastPos, const QPointF &pos) +{ + d->zoomAction->cursorMovedAbsolute(lastPos, pos); + d->rotateAction->cursorMovedAbsolute(lastPos, pos); +} + +void KisZoomAndRotateAction::inputEvent(QEvent *event) +{ + d->zoomAction->inputEvent(event); + d->rotateAction->inputEvent(event); +} + +KisInputActionGroup KisZoomAndRotateAction::inputActionGroup(int shortcut) const +{ + Q_UNUSED(shortcut); + return ViewTransformActionGroup; +} diff --git a/libs/ui/input/kis_zoom_and_rotate_action.h b/libs/ui/input/kis_zoom_and_rotate_action.h new file mode 100644 index 0000000000..73da68bf9e --- /dev/null +++ b/libs/ui/input/kis_zoom_and_rotate_action.h @@ -0,0 +1,48 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2019 Sharaf Zaman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KIS_ZOOM_AND_ROTATE_ACTION_H +#define KIS_ZOOM_AND_ROTATE_ACTION_H + +#include "kis_abstract_input_action.h" + + +/** + * @brief This class merely deligates the actions to KisZoomAction + * _and_ KisRotateCanvasAction at the same time. + */ +class KisZoomAndRotateAction : public KisAbstractInputAction +{ +public: + KisZoomAndRotateAction(); + int priority() const override; + + void activate(int shortcut) override; + void deactivate(int shortcut) override; + void begin(int shortcut, QEvent *event) override; + void cursorMovedAbsolute(const QPointF &lastPos, const QPointF &pos) override; + void inputEvent(QEvent* event) override; + + KisInputActionGroup inputActionGroup(int shortcut) const override; +private: + class Private; + const Private *d; +}; + +#endif // KIS_ZOOM_AND_ROTATE_ACTION_H diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index b81b35d856..e6d48e6f38 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2151 +1,2147 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" #endif KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp && qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } 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); } int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const { return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0); } void KisConfig::setPreferredVectorImportResolutionPPI(int value) const { m_cfg.writeEntry("preferredVectorImportResolution", value); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",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(); } QString KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); KoColor kol(KoColorSpaceRegistry::instance()->rgb8()); kol.fromQColor(col); QString xml = kol.toXML(); return (defaultValue ? xml : m_cfg.readEntry("mdiBackgroundColorXML", xml)); } void KisConfig::setMDIBackgroundColor(const QString &v) const { m_cfg.writeEntry("mdiBackgroundColorXML", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg(true); QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); 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::forcePaletteColors(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false)); } void KisConfig::setForcePaletteColors(bool forcePaletteColors) { m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } - //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 + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + + return kritarc.value("OpenGLRenderer", "auto").toString() != "none"; } -void KisConfig::setUseOpenGL(bool useOpenGL) const +void KisConfig::disableOpenGL() const { -#ifdef Q_OS_WIN - m_cfg.writeEntry("useOpenGLWindows", useOpenGL); -#else - m_cfg.writeEntry("useOpenGL", useOpenGL); -#endif + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + + kritarc.setValue("OpenGLRenderer", "none"); } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); m_cfg.writeEntry("globalSnapToPixel", config.toPixel()); } qint32 KisConfig::checkSize(bool defaultValue) const { 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); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return useWin8PointerInputNoApp(&kritarc, defaultValue); #else return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #endif #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setUseWin8PointerInputNoApp(&kritarc, value); #else m_cfg.writeEntry("useWin8PointerInput", value); #endif } #else Q_UNUSED(value) #endif } bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue) { return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool(); } void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value) { settings->setValue("useWin8PointerInput", value); } bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false)); } void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value) { m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(','))); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); const QString xmlData = exportConfigurationXML(filterId, defaultValue); cfg->fromXML(xmlData); return cfg; } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const { KisOcioConfiguration cfg; if (!defaultValue) { cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0); cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()); cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()); cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString()); cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString()); cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString()); cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString()); } return cfg; } void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg) { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode); m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath); m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath); m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace); m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice); m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView); m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } int KisConfig::layerThumbnailSize(bool defaultValue) const { return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20)); } void KisConfig::setLayerThumbnailSize(int size) { m_cfg.writeEntry("layerThumbnailSize", size); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } bool KisConfig::kineticScrollingEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true)); } void KisConfig::setKineticScrollingEnabled(bool value) { m_cfg.writeEntry("KineticScrollingEnabled", value); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2)); } void KisConfig::setKineticScrollingGesture(int gesture) { m_cfg.writeEntry("KineticScrollingGesture", gesture); } int KisConfig::kineticScrollingSensitivity(bool defaultValue) const { return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); } void KisConfig::setKineticScrollingSensitivity(int sensitivity) { m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); } bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false)); } void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar) { m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setDisableAVXOptimizations(bool value) { m_cfg.writeEntry("disableAVXOptimizations", value); } bool KisConfig::disableAVXOptimizations(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } QColor KisConfig::defaultAssistantsColor(bool defaultValue) const { static const QColor defaultColor = QColor(176, 176, 176, 255); return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor); } void KisConfig::setDefaultAssistantsColor(const QColor &color) const { m_cfg.writeEntry("defaultAssistantsColor", color); } bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false); } void KisConfig::setAutoSmoothBezierCurves(bool value) { m_cfg.writeEntry("autoSmoothBezierCurves", value); } bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false); } void KisConfig::setActivateTransformToolAfterPaste(bool value) { m_cfg.writeEntry("activateTransformToolAfterPaste", value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return rootSurfaceFormat(&kritarc, defaultValue); } void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setRootSurfaceFormat(&kritarc, value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue) { QString textValue = "bt709-g22"; if (!defaultValue) { textValue = displayrc->value("rootSurfaceFormat", textValue).toString(); } return textValue == "bt709-g10" ? BT709_G10 : textValue == "bt2020-pq" ? BT2020_PQ : BT709_G22; } void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value) { const QString textValue = value == BT709_G10 ? "bt709-g10" : value == BT2020_PQ ? "bt2020-pq" : "bt709-g22"; displayrc->setValue("rootSurfaceFormat", textValue); } bool KisConfig::useZip64(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("UseZip64", false); } void KisConfig::setUseZip64(bool value) { m_cfg.writeEntry("UseZip64", value); } #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 ea97177a09..2ac7ed0436 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,636 +1,636 @@ /* * 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 #include #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class QSettings; class KisOcioConfiguration; class KRITAUI_EXPORT KisConfig { public: /** * @brief KisConfig create a kisconfig object * @param readOnly if true, there will be no call to sync when the object is deleted. * Any KisConfig object created in a thread must be read-only. */ KisConfig(bool readOnly); ~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; int preferredVectorImportResolutionPPI(bool defaultValue = false) const; void setPreferredVectorImportResolutionPPI(int value) 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); bool forcePaletteColors(bool defaultValue = false) const; void setForcePaletteColors(bool forcePaletteColors); 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; + void disableOpenGL() 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; 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 forceAlwaysFullSizedOutline(bool defaultValue = false) const; void setForceAlwaysFullSizedOutline(bool value) const; enum SessionOnStartup { SOS_BlankSession, SOS_PreviousSession, SOS_ShowSessionManager }; SessionOnStartup sessionOnStartup(bool defaultValue = false) const; void setSessionOnStartup(SessionOnStartup value); bool saveSessionOnQuit(bool defaultValue) const; void setSaveSessionOnQuit(bool value); 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); static bool useWin8PointerInputNoApp(QSettings *settings, bool defaultValue = false); static void setUseWin8PointerInputNoApp(QSettings *settings, bool value); bool useRightMiddleTabletButtonWorkaround(bool defaultValue = false) const; void setUseRightMiddleTabletButtonWorkaround(bool value); 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 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 exportConfigurationXML(const QString &filterId, bool defaultValue = false) const; KisPropertiesConfigurationSP 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); KisOcioConfiguration ocioConfiguration(bool defaultValue = false) const; void setOcioConfiguration(const KisOcioConfiguration &cfg); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) 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); int layerThumbnailSize(bool defaultValue = false) const; void setLayerThumbnailSize(int size); 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 { RASTER_LAYER = 0, CANVAS_COLOR = 1, FILL_LAYER = 2 }; 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 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); QString getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QString & 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); bool kineticScrollingEnabled(bool defaultValue = false) const; void setKineticScrollingEnabled(bool enabled); int kineticScrollingGesture(bool defaultValue = false) const; void setKineticScrollingGesture(int kineticScroll); int kineticScrollingSensitivity(bool defaultValue = false) const; void setKineticScrollingSensitivity(int sensitivity); bool kineticScrollingHiddenScrollbars(bool defaultValue = false) const; void setKineticScrollingHideScrollbars(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; void setDisableAVXOptimizations(bool value); bool disableAVXOptimizations(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); 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); QColor defaultAssistantsColor(bool defaultValue = false) const; void setDefaultAssistantsColor(const QColor &color) const; bool autoSmoothBezierCurves(bool defaultValue = false) const; void setAutoSmoothBezierCurves(bool value); bool activateTransformToolAfterPaste(bool defaultValue = false) const; void setActivateTransformToolAfterPaste(bool value); enum RootSurfaceFormat { BT709_G22 = 0, BT709_G10, BT2020_PQ }; RootSurfaceFormat rootSurfaceFormat(bool defaultValue = false) const; void setRootSurfaceFormat(RootSurfaceFormat value); static RootSurfaceFormat rootSurfaceFormat(QSettings *displayrc, bool defaultValue = false); static void setRootSurfaceFormat(QSettings *displayrc, RootSurfaceFormat value); bool useZip64(bool defaultValue = false) const; void setUseZip64(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 management 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; bool m_readOnly; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index c5c25ae838..7fdac820ed 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,999 +1,999 @@ /* * Copyright (C) 2006 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_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "dialogs/KisDlgChangeCloneSource.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_node_commands.h" #include "kis_change_file_layer_command.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_processing_applicator.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_imageFlatten(0) , m_imageMergeLayer(0) , m_groupLayersSave(0) , m_imageResizeToLayer(0) , m_flattenLayer(0) , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) , m_layerStyle(0) { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { emit sigLayerActivated(layer); layersUpdated(); if (layer) { m_view->canvasResourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageSP image = m_view->image(); KisLayerSP layer = activeLayer(); const bool isGroupLayer = layer && layer->inherits("KisGroupLayer"); m_imageMergeLayer->setText( isGroupLayer ? i18nc("@action:inmenu", "Merge Group") : i18nc("@action:inmenu", "Merge with Layer Below")); m_flattenLayer->setVisible(!isGroupLayer); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); if (!layer) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); - KisGeneratorLayerSP groupLayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); - KisFileLayerSP filterLayer = KisFileLayerSP(dynamic_cast(layer.data())); + KisGeneratorLayerSP generatorLayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); + KisFileLayerSP fileLayer = KisFileLayerSP(dynamic_cast(layer.data())); if (adjustmentLayer && !multipleLayersSelected) { KisPaintDeviceSP dev = adjustmentLayer->projection(); KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(adjustmentLayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { adjustmentLayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(adjustmentLayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); adjustmentLayer->setDirty(); } } } - else if (groupLayer && !multipleLayersSelected) { - KisFilterConfigurationSP configBefore(groupLayer->filter()); + else if (generatorLayer && !multipleLayersSelected) { + KisFilterConfigurationSP configBefore(generatorLayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); - KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(groupLayer->name(), m_view, m_view->mainWindow(), groupLayer, configBefore); + KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(generatorLayer->name(), m_view, m_view->mainWindow(), generatorLayer, configBefore); dlg->setCaption(i18n("Fill Layer Properties")); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setConfiguration(configBefore.data()); dlg->resize(dlg->minimumSizeHint()); Qt::WindowFlags flags = dlg->windowFlags(); dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dlg->show(); } - else if (filterLayer && !multipleLayersSelected){ + else if (fileLayer && !multipleLayersSelected){ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); - QString fileNameOld = filterLayer->fileName(); - KisFileLayer::ScalingMethod scalingMethodOld = filterLayer->scalingMethod(); - KisDlgFileLayer dlg(basePath, filterLayer->name(), m_view->mainWindow()); + QString fileNameOld = fileLayer->fileName(); + KisFileLayer::ScalingMethod scalingMethodOld = fileLayer->scalingMethod(); + KisDlgFileLayer dlg(basePath, fileLayer->name(), m_view->mainWindow()); dlg.setCaption(i18n("File Layer Properties")); dlg.setFileName(fileNameOld); dlg.setScalingMethod(scalingMethodOld); if (dlg.exec() == QDialog::Accepted) { const QString fileNameNew = dlg.fileName(); KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution(); if(fileNameNew.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } - filterLayer->setName(dlg.layerName()); + fileLayer->setName(dlg.layerName()); if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) { KisChangeFileLayerCmd *cmd - = new KisChangeFileLayerCmd(filterLayer, + = new KisChangeFileLayerCmd(fileLayer, basePath, fileNameOld, scalingMethodOld, basePath, fileNameNew, scalingMethodNew); m_view->undoAdapter()->addCommand(cmd); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } } void KisLayerManager::changeCloneSource() { QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.isEmpty()) { return; } QList cloneLayers; KisNodeSP node; Q_FOREACH (node, selectedNodes) { KisCloneLayerSP cloneLayer(qobject_cast(node.data())); if (cloneLayer) { cloneLayers << cloneLayer; } } if (cloneLayers.isEmpty()) { return; } KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view); dialog->setCaption(i18n("Change Clone Layer")); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source->prevSibling(); while (parent && !parent->allowAsChild(layer)) { above = above ? above->parent() : source->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::convertLayerToFileLayer(KisNodeSP source) { KisImageSP image = m_view->image(); if (!image) return; QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); dlg.setWindowTitle(i18n("Save layers to...")); QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location.")); lbl->setWordWrap(true); layout->addWidget(lbl); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); if (m_view->document()->url().isLocalFile()) { - QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).baseName(); - location.setFile(location.dir(), location.baseName() + "_" + source->name() + ".png"); + QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).completeBaseName(); + location.setFile(location.dir(), location.completeBaseName() + "_" + source->name() + ".png"); urlRequester->setFileName(location.absoluteFilePath()); } else { const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png"); urlRequester->setFileName(proposedFileName); } // We don't want .kra files as file layers, Krita cannot handle the load. QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); int i = mimes.indexOf(KIS_MIME_TYPE); if (i >= 0 && i < mimes.size()) { mimes.removeAt(i); } urlRequester->setMimeTypeFilters(mimes); layout->addWidget(urlRequester); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QScopedPointer doc(KisPart::instance()->createDocument()); QRect bounds = source->exactBounds(); KisImageSP dst = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->projection()->compositionSourceColorSpace(), source->name()); dst->setResolution(image->xRes(), image->yRes()); doc->setFileBatchMode(false); doc->setCurrentImage(dst); KisNodeSP node = source->clone(); dst->addNode(node); dst->initialRefreshGraph(); dst->cropImage(bounds); dst->waitForDone(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1()); if (!r) { qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage(); } else { QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString relativePath = QDir(basePath).relativeFilePath(path); KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8); fileLayer->setX(bounds.x()); fileLayer->setY(bounds.y()); KisNodeSP dstParent = source->parent(); KisNodeSP dstAboveThis = source->prevSibling(); m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis); m_commandsAdapter->endMacro(); } doc->closeUrl(false); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); if (parent->inherits("KisGroupLayer") && parent->collapsed()) { above = parent; parent = parent->parent(); return; } while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator); } KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, group, false, 0); return group; } KisNodeSP KisLayerManager::addCloneLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisNodeSP node = new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node, true, 0); return node; } KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return 0; if (!m_view->document()) return 0; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Add Layer")); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow()); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! applicator.cancel(); } else { adjl->setName(dlg.layerName()); applicator.end(); } return adjl; } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer, true, applicator); return layer; } KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor(); KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0); KisFilterConfigurationSP defaultConfig = dlg.configuration(); defaultConfig->setProperty("color", currentForeground); dlg.setConfiguration(defaultConfig); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void KisLayerManager::flattenImage() { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(m_view->activeNode()); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image) { bool result = false; if (currentNode->inherits("KisGroupLayer")) { KisGroupLayer *layer = qobject_cast(currentNode.data()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false); image->flattenLayer(layer); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (tryMergeSelectionMasks(m_view->activeNode(), image)) { // already done! } else if (tryFlattenGroupLayer(m_view->activeNode(), image)) { // already done! } else { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (prevLayer->userLocked()) { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is locked "), QIcon(), 2000, KisFloatingMessage::Low); } else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; convertNodeToPaintLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); - QString basename = f.baseName(); + QString basename = f.completeBaseName(); KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return 0; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->canvasResourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index a106ce8a95..ea6ecf1558 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1578 +1,1578 @@ /* * 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_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "KisNodeDisplayModeAdapter.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "kis_shape_layer.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" #include #include #include struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) , nodeDisplayModeAdapter(new KisNodeDisplayModeAdapter()) , lastRequestedIsolatedModeStatus(false) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; QScopedPointer nodeDisplayModeAdapter; KisAction *showInTimeline; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; bool lastRequestedIsolatedModeStatus; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); //if (!shape) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); //if (!shapeLayer) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeActionImageStatusChange())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP,KisNodeList)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP,KisNodeList))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = 0; action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("mirrorAllNodesX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesX())); action = actionManager->createAction("mirrorAllNodesY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("save_vector_node_to_svg"); connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage())); action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); action = actionManager->createAction("show_in_timeline"); action->setCheckable(true); connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool))); m_d->showInTimeline = action; NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(QString)), this, SLOT(createNode(QString))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); CONVERT_NODE_ACTION_2("convert_to_file_layer", "KisFileLayer", QStringList() << "KisGroupLayer" << "KisFileLayer" << "KisCloneLayer"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(QString)), this, SLOT(convertNode(QString))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("toggle_layer_visibility"); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); action = actionManager->createAction("toggle_layer_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); action = actionManager->createAction("toggle_layer_inherit_alpha"); connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); action = actionManager->createAction("toggle_layer_alpha_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryRestartIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); if (m && m->active() && l && l->selectionMask()) { l->selectionMask()->setActive(false); } } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); if (checked && activeNode) { // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } m_d->lastRequestedIsolatedModeStatus = checked; } void KisNodeManager::slotUpdateIsolateModeActionImageStatusChange() { slotUpdateIsolateModeAction(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (this->activeNode() && bool(isolatedRootNode) != m_d->lastRequestedIsolatedModeStatus) { slotTryRestartIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryRestartIsolatedMode() { /** * It might be that we have multiple Krita windows open. In such a case * only the currently active one should restart isolated mode */ if (!m_d->view->mainWindow()->isActiveWindow()) return; KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode && !m_d->lastRequestedIsolatedModeStatus) return; this->toggleIsolateMode(true); } KisNodeSP KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return 0; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN_VALUE(activeNode, 0); // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { return m_d->layerManager.addPaintLayer(activeNode); } else if (nodeType == "KisGroupLayer") { return m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { return m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { return m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { return m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { return m_d->layerManager.addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { return m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { return m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { return m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { return m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { return m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { return m_d->layerManager.addFileLayer(activeNode); } return 0; } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } void KisNodeManager::slotShowHideTimeline(bool value) { Q_FOREACH (KisNodeSP node, selectedNodes()) { node->setUseInTimeline(value); } } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP node = createNode("KisPaintLayer"); return dynamic_cast(node.data()); } void KisNodeManager::convertNode(const QString &nodeType) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); bool result = false; if (nodeType == "KisSelectionMask") { result = !m_d->maskManager.createSelectionMask(activeNode, copyFrom, true).isNull(); } else if (nodeType == "KisFilterMask") { result = !m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true).isNull(); } else if (nodeType == "KisTransparencyMask") { result = !m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true).isNull(); } m_d->commandsAdapter.endMacro(); if (!result) { m_d->view->blockUntilOperationsFinishedForced(m_d->imageView->image()); m_d->commandsAdapter.undoLastCommand(); } } else if (nodeType == "KisFileLayer") { m_d->layerManager.convertLayerToFileLayer(activeNode); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); KIS_SAFE_ASSERT_RECOVER_RETURN(dummiesFacade); const bool nodeVisible = !isNodeHidden(node, !m_d->nodeDisplayModeAdapter->showGlobalSelectionMask()); if (!nodeVisible) { return; } KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; KisSelectionMask *selectionMask = dynamic_cast(node.data()); const bool nodeHasVectorAbilities = node->inherits("KisShapeLayer") || (selectionMask && selectionMask->selection()->hasShapeSelection()); if (nodeHasVectorAbilities) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); { KisSignalsBlocker b(m_d->showInTimeline); m_d->showInTimeline->setChecked(node->useInTimeline()); } } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if ((selectedNodes().size() > 1 && node->inherits("KisLayer")) || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } void KisNodeManager::changeCloneSource() { m_d->layerManager.changeCloneSource(); } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeName(KisNodeSP node, const QString &name) { if (!node) return; if (node->name() == name) return; m_d->commandsAdapter.setNodeName(node, name); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity) { if (!node) return; if (node->opacity() == opacity) return; m_d->commandsAdapter.setOpacity(node, opacity); } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } KisNodeDisplayModeAdapter *KisNodeManager::nodeDisplayModeAdapter() const { return m_d->nodeDisplayModeAdapter.data(); } bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden) { if (node && node->isFakeNode()) { return true; } if (isGlobalSelectionHidden && dynamic_cast(node.data()) && (!node->parent() || !node->parent()->parent())) { return true; } return false; } bool KisNodeManager::trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const { const KisPaintLayer *paintLayer = dynamic_cast(node.data()); if (paintLayer) { const auto onionSkinOn = KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, true); if (properties.contains(onionSkinOn)) { const KisPaintDeviceSP &paintDevice = paintLayer->paintDevice(); if (paintDevice && paintDevice->defaultPixel().opacityU8() == 255) { m_d->view->showFloatingMessage(i18n("Onion skins require a layer with transparent background."), QIcon()); return false; } } } KisNodePropertyListCommand::setNodePropertiesNoUndo(node, image, properties); return true; } void KisNodeManager::nodeOpacityChanged(qreal opacity) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity)); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && (nodeJuggler->isEnded() || !nodeJuggler->canMergeAction(actionName)))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal, m_d->view->selection()); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical, m_d->view->selection()); } void KisNodeManager::mirrorAllNodesX() { KisNodeSP node = m_d->view->image()->root(); mirrorNode(node, kundo2_i18n("Mirror All Layers X"), - Qt::Vertical, m_d->view->selection()); + Qt::Horizontal, m_d->view->selection()); } void KisNodeManager::mirrorAllNodesY() { KisNodeSP node = m_d->view->image()->root(); mirrorNode(node, kundo2_i18n("Mirror All Layers Y"), Qt::Vertical, m_d->view->selection()); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation, KisSelectionSP selection) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor; if (selection) { visitor = new KisMirrorProcessingVisitor(selection, orientation); } else { visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); } if (!selection) { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); if (!doc->exportDocumentSync(url, mimefilter.toLatin1())) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not save the layer. %1", doc->errorMessage().toUtf8().data()), QMessageBox::Ok); } } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); m_d->saveDeviceAsImage(node->projection(), node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } #include "SvgWriter.h" void KisNodeManager::saveVectorLayerAsImage() { KisShapeLayerSP shapeLayer = qobject_cast(activeNode().data()); if (!shapeLayer) { return; } KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18nc("@title:window", "Export to SVG")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; const QSizeF sizeInPx = m_d->view->image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(), sizeInPx.height() / m_d->view->image()->yRes()); QList shapes = shapeLayer->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); if (!writer.save(filename, sizeInPt, true)) { QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1", filename)); } } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable", parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.", parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::toggleLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isLocked = active->userLocked(); for (auto &node : nodes) { node->setUserLocked(!isLocked); } } void KisNodeManager::toggleVisibility() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isVisible = active->visible(); for (auto &node : nodes) { node->setVisible(!isVisible); node->setDirty(); } } void KisNodeManager::toggleAlphaLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaLocked = layer->alphaLocked(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->setAlphaLocked(!isAlphaLocked); } } } void KisNodeManager::toggleInheritAlpha() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaDisabled = layer->alphaChannelDisabled(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->disableAlphaChannel(!isAlphaDisabled); node->setDirty(); } } } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (nodes2.size() == 0) return; if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index 20fd3d9e4a..ee035ecaf4 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,759 +1,760 @@ + /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * The outline algorithm uses the limn algorithm of fontutils by * Karl Berry and Kathryn Hargreaves * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_manager.h" #include #include #include #include #include #include #include #include #include #include #include "KoCanvasController.h" #include "KoChannelInfo.h" #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_adjustment_layer.h" #include "kis_node_manager.h" #include "canvas/kis_canvas2.h" #include "kis_config.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_debug.h" #include "kis_fill_painter.h" #include "kis_group_layer.h" #include "kis_layer.h" #include "kis_statusbar.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include "kis_selection.h" #include "kis_types.h" #include "kis_canvas_resource_provider.h" #include "kis_undo_adapter.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "commands/kis_selection_commands.h" #include "kis_selection_mask.h" #include "flake/kis_shape_layer.h" #include "kis_selection_decoration.h" #include "canvas/kis_canvas_decoration.h" #include "kis_node_commands_adapter.h" #include "kis_iterator_ng.h" #include "kis_clipboard.h" #include "KisViewManager.h" #include "kis_selection_filters.h" #include "kis_figure_painting_tool_helper.h" #include "KisView.h" #include "dialogs/kis_dlg_stroke_selection_properties.h" #include "actions/kis_selection_action_factories.h" #include "actions/KisPasteActionFactories.h" #include "kis_action.h" #include "kis_action_manager.h" #include "operations/kis_operation_configuration.h" //new #include "kis_node_query_path.h" #include "kis_tool_shape.h" KisSelectionManager::KisSelectionManager(KisViewManager * view) : m_view(view), m_doc(0), m_imageView(0), m_adapter(new KisNodeCommandsAdapter(view)), m_copy(0), m_copyMerged(0), m_cut(0), m_paste(0), m_pasteNew(0), m_cutToNewLayer(0), m_selectAll(0), m_deselect(0), m_clear(0), m_reselect(0), m_invert(0), m_copyToNewLayer(0), m_fillForegroundColor(0), m_fillBackgroundColor(0), m_fillPattern(0), m_imageResizeToSelection(0), m_selectionDecoration(0) { m_clipboard = KisClipboard::instance(); } KisSelectionManager::~KisSelectionManager() { } void KisSelectionManager::setup(KisActionManager* actionManager) { m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut())); m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy())); m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste())); KisAction *action = actionManager->createAction("copy_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(copySharp())); action = actionManager->createAction("cut_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(cutSharp())); m_pasteNew = actionManager->createAction("paste_new"); connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew())); m_pasteAt = actionManager->createAction("paste_at"); connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt())); m_pasteAsReference = actionManager->createAction("paste_as_reference"); connect(m_pasteAsReference, SIGNAL(triggered()), this, SLOT(pasteAsReference())); m_copyMerged = actionManager->createAction("copy_merged"); connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged())); m_selectAll = actionManager->createAction("select_all"); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); m_deselect = actionManager->createAction("deselect"); connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect())); m_clear = actionManager->createAction("clear"); connect(m_clear, SIGNAL(triggered()), SLOT(clear())); m_reselect = actionManager->createAction("reselect"); connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect())); m_invert = actionManager->createAction("invert_selection"); m_invert->setOperationID("invertselection"); actionManager->registerOperation(new KisInvertSelectionOperation); m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer"); connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer())); m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer"); connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer())); m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color"); connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor())); m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color"); connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor())); m_fillPattern = actionManager->createAction("fill_selection_pattern"); connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern())); m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity"); connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity())); m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity"); connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity())); m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity"); connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity())); m_strokeShapes = actionManager->createAction("stroke_shapes"); connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes())); m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection"); connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection())); m_toggleDisplaySelection->setChecked(true); m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection"); connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection())); action = actionManager->createAction("edit_selection"); connect(action, SIGNAL(triggered()), SLOT(editSelection())); action = actionManager->createAction("convert_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection())); action = actionManager->createAction("convert_to_raster_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection())); action = actionManager->createAction("convert_shapes_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection())); action = actionManager->createAction("convert_selection_to_shape"); connect(action, SIGNAL(triggered()), SLOT(convertToShape())); m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode"); connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration())); m_strokeSelected = actionManager->createAction("stroke_selection"); connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection())); QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged())); } void KisSelectionManager::setView(QPointerimageView) { if (m_imageView && m_imageView->canvasBase()) { disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), this, SLOT(clipboardDataChanged())); KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection(); selection->disconnect(this, SLOT(shapeSelectionChanged())); KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (decoration) { disconnect(SIGNAL(currentSelectionChanged()), decoration); } m_imageView->image()->undoAdapter()->disconnect(this); m_selectionDecoration = 0; } m_imageView = imageView; if (m_imageView) { connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()), Qt::UniqueConnection); KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (!decoration) { decoration = new KisSelectionDecoration(m_imageView); decoration->setVisible(true); m_imageView->canvasBase()->addDecoration(decoration); } m_selectionDecoration = decoration; connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged())); connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), SLOT(clipboardDataChanged())); } } void KisSelectionManager::clipboardDataChanged() { m_view->updateGUI(); } bool KisSelectionManager::havePixelsSelected() { KisSelectionSP activeSelection = m_view->selection(); return activeSelection && !activeSelection->selectedRect().isEmpty(); } bool KisSelectionManager::havePixelsInClipboard() { return m_clipboard->hasClip(); } bool KisSelectionManager::haveShapesSelected() { if (m_view && m_view->canvasBase()) { return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0; } return false; } bool KisSelectionManager::haveShapesInClipboard() { KoSvgPaste paste; return paste.hasShapes(); } bool KisSelectionManager::haveAnySelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection(); } bool KisSelectionManager::haveShapeSelectionWithShapes() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasShapeSelection(); } bool KisSelectionManager::haveRasterSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection() && !selection->hasShapeSelection(); } void KisSelectionManager::updateGUI() { Q_ASSERT(m_view); Q_ASSERT(m_clipboard); if (!m_view || !m_clipboard) return; bool havePixelsSelected = this->havePixelsSelected(); bool havePixelsInClipboard = this->havePixelsInClipboard(); bool haveShapesSelected = this->haveShapesSelected(); bool haveShapesInClipboard = this->haveShapesInClipboard(); bool haveDevice = m_view->activeDevice(); KisLayerSP activeLayer = m_view->activeLayer(); KisImageWSP image = activeLayer ? activeLayer->image() : 0; bool canReselect = image && image->canReselectGlobalSelection(); bool canDeselect = image && image->globalSelection(); m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected); m_cut->setEnabled(havePixelsSelected || haveShapesSelected); m_copy->setEnabled(havePixelsSelected || haveShapesSelected); m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard); m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard); // FIXME: how about pasting shapes? m_pasteNew->setEnabled(havePixelsInClipboard); m_pasteAsReference->setEnabled(haveDevice); m_selectAll->setEnabled(true); m_deselect->setEnabled(canDeselect); m_reselect->setEnabled(canReselect); // m_load->setEnabled(true); // m_save->setEnabled(havePixelsSelected); updateStatusBar(); emit signalUpdateGUI(); } void KisSelectionManager::updateStatusBar() { if (m_view && m_view->statusBar()) { m_view->statusBar()->setSelection(m_view->image()); } } void KisSelectionManager::selectionChanged() { m_view->updateGUI(); emit currentSelectionChanged(); } void KisSelectionManager::cut() { KisCutCopyActionFactory factory; factory.run(true, false, m_view); } void KisSelectionManager::copy() { KisCutCopyActionFactory factory; factory.run(false, false, m_view); } void KisSelectionManager::cutSharp() { KisCutCopyActionFactory factory; factory.run(true, true, m_view); } void KisSelectionManager::copySharp() { KisCutCopyActionFactory factory; factory.run(false, true, m_view); } void KisSelectionManager::copyMerged() { KisCopyMergedActionFactory factory; factory.run(m_view); } void KisSelectionManager::paste() { KisPasteActionFactory factory; factory.run(false, m_view); } void KisSelectionManager::pasteAt() { KisPasteActionFactory factory; factory.run(true, m_view); } void KisSelectionManager::pasteAsReference() { KisPasteReferenceActionFactory factory; factory.run(m_view); } void KisSelectionManager::pasteNew() { KisPasteNewActionFactory factory; factory.run(m_view); } void KisSelectionManager::selectAll() { KisSelectAllActionFactory factory; factory.run(m_view); } void KisSelectionManager::deselect() { KisDeselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::invert() { if(m_invert) m_invert->trigger(); } void KisSelectionManager::reselect() { KisReselectActionFactory factory; factory.run(m_view); } #include #include void KisSelectionManager::editSelection() { KisSelectionSP selection = m_view->selection(); if (!selection) return; KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask"); KIS_SAFE_ASSERT_RECOVER_RETURN(action); if (!action->isChecked()) { action->setChecked(true); emit action->toggled(true); emit action->triggered(true); } KisNodeSP node = selection->parentNode(); KIS_SAFE_ASSERT_RECOVER_RETURN(node); m_view->nodeManager()->slotNonUiActivatedNode(node); if (selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection); KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID); QList shapes = shapeSelection->shapes(); if (shapes.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes"); return; } Q_FOREACH (KoShape *shape, shapes) { m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape); } } else { KoToolManager::instance()->switchToolRequested("KisToolTransform"); } } void KisSelectionManager::convertToVectorSelection() { KisSelectionToVectorActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToRasterSelection() { KisSelectionToRasterActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertShapesToVectorSelection() { KisShapesToVectorSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToShape() { KisSelectionToShapeActionFactory factory; factory.run(m_view); } void KisSelectionManager::clear() { KisClearActionFactory factory; factory.run(m_view); } void KisSelectionManager::fillForegroundColor() { KisFillActionFactory factory; factory.run("fg", m_view); } void KisSelectionManager::fillBackgroundColor() { KisFillActionFactory factory; factory.run("bg", m_view); } void KisSelectionManager::fillPattern() { KisFillActionFactory factory; factory.run("pattern", m_view); } void KisSelectionManager::fillForegroundColorOpacity() { KisFillActionFactory factory; factory.run("fg_opacity", m_view); } void KisSelectionManager::fillBackgroundColorOpacity() { KisFillActionFactory factory; factory.run("bg_opacity", m_view); } void KisSelectionManager::fillPatternOpacity() { KisFillActionFactory factory; factory.run("pattern_opacity", m_view); } void KisSelectionManager::copySelectionToNewLayer() { copy(); paste(); } void KisSelectionManager::cutToNewLayer() { cut(); paste(); } void KisSelectionManager::toggleDisplaySelection() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); m_selectionDecoration->toggleVisibility(); m_toggleDisplaySelection->blockSignals(true); m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible()); m_toggleDisplaySelection->blockSignals(false); emit displaySelectionChanged(); } bool KisSelectionManager::displaySelection() { return m_toggleDisplaySelection->isChecked(); } void KisSelectionManager::shapeSelectionChanged() { KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager(); KoSelection * selection = shapeManager->selection(); QList selectedShapes = selection->selectedShapes(); KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray)); Q_FOREACH (KoShape* shape, shapeManager->shapes()) { if (dynamic_cast(shape->parent())) { if (selectedShapes.contains(shape)) shape->setStroke(border); else shape->setStroke(KoShapeStrokeSP()); } } m_view->updateGUI(); } void KisSelectionManager::imageResizeToSelection() { KisImageResizeToSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::paintSelectedShapes() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes(); KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8); KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes"); m_adapter->beginMacro(actionName); m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); KisFigurePaintingToolHelper helper(actionName, image, paintLayer.data(), m_view->canvasResourceProvider()->resourceManager(), KisPainter::StrokeStyleBrush, KisPainter::FillStyleNone); Q_FOREACH (KoShape* shape, shapes) { QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes()); QPainterPath mapedOutline = matrix.map(shape->outline()); helper.paintPainterPath(mapedOutline); } m_adapter->endMacro(); } void KisSelectionManager::slotToggleSelectionDecoration() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); KisSelectionDecoration::Mode mode = m_selectionDecoration->mode() ? KisSelectionDecoration::Ants : KisSelectionDecoration::Mask; m_selectionDecoration->setMode(mode); emit displaySelectionChanged(); } bool KisSelectionManager::showSelectionAsMask() const { if (m_selectionDecoration) { return m_selectionDecoration->mode() == KisSelectionDecoration::Mask; } return false; } void KisSelectionManager::slotStrokeSelection() { KisImageWSP image = m_view->image(); - if (!image ) { - + if (!image ) { return; } + KisNodeSP currentNode = m_view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); bool isVectorLayer = false; if (currentNode->inherits("KisShapeLayer")) { isVectorLayer = true; } QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer); if (dlg->exec() == QDialog::Accepted) { StrokeSelectionOptions params = dlg->getParams(); if (params.brushSelected){ KisStrokeBrushSelectionActionFactory factory; factory.run(m_view, params); } else { KisStrokeSelectionActionFactory factory; factory.run(m_view, params); } } delete dlg; } #include "kis_image_barrier_locker.h" #include "kis_selection_tool_helper.h" void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action) { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) { return; } KUndo2MagicString actionName; KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisCanvas2 *canvas = m_view->canvasBase(); { KisImageBarrierLocker locker(image); KisPaintDeviceSP device = node->projection(); if (!device) device = node->paintDevice(); if (!device) device = node->original(); if (!device) return; QRect rc = device->exactBounds(); if (rc.isEmpty()) return; KIS_ASSERT_RECOVER_RETURN(canvas); /** * If there is nothing selected, just create a new selection */ if (!canvas->imageView()->selection()) { action = SELECTION_REPLACE; } switch (action) { case SELECTION_ADD: actionName = kundo2_i18n("Select Opaque (Add)"); break; case SELECTION_SUBTRACT: actionName = kundo2_i18n("Select Opaque (Subtract)"); break; case SELECTION_INTERSECT: actionName = kundo2_i18n("Select Opaque (Intersect)"); break; case SELECTION_SYMMETRICDIFFERENCE: actionName = kundo2_i18n("Select Opaque (Symmetric Difference)"); break; default: actionName = kundo2_i18n("Select Opaque"); break; } qint32 x, y, w, h; rc.getRect(&x, &y, &w, &h); const KoColorSpace * cs = device->colorSpace(); KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w); for (int row = y; row < h + y; ++row) { do { *selIter->rawData() = cs->opacityU8(deviter->oldRawData()); } while (deviter->nextPixel() && selIter->nextPixel()); deviter->nextRow(); selIter->nextRow(); } } KisSelectionToolHelper helper(canvas, actionName); tmpSel->invalidateOutlineCache(); helper.selectPixelSelection(tmpSel, action); } diff --git a/libs/ui/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp index a946163fc1..1fe495b541 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.cpp +++ b/libs/ui/opengl/KisOpenGLModeProber.cpp @@ -1,272 +1,333 @@ /* * Copyright (c) 2017 Alvin Wong * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisOpenGLModeProber.h" #include #include #include #include #include #include Q_GLOBAL_STATIC(KisOpenGLModeProber, s_instance) KisOpenGLModeProber::KisOpenGLModeProber() { } KisOpenGLModeProber::~KisOpenGLModeProber() { } KisOpenGLModeProber *KisOpenGLModeProber::instance() { return s_instance; } bool KisOpenGLModeProber::useHDRMode() const { return isFormatHDR(QSurfaceFormat::defaultFormat()); } QSurfaceFormat KisOpenGLModeProber::surfaceformatInUse() const { // TODO: use information provided by KisOpenGL instead QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext(); QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat(); return format; } const KoColorProfile *KisOpenGLModeProber::rootSurfaceColorProfile() const { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709SRGBProfile(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const KisSurfaceColorSpace surfaceColorSpace = surfaceformatInUse().colorSpace(); if (surfaceColorSpace == KisSurfaceColorSpace::sRGBColorSpace) { // use the default one! #ifdef HAVE_HDR } else if (surfaceColorSpace == KisSurfaceColorSpace::scRGBColorSpace) { profile = KoColorSpaceRegistry::instance()->p709G10Profile(); } else if (surfaceColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace) { profile = KoColorSpaceRegistry::instance()->p2020PQProfile(); #endif } #endif return profile; } namespace { struct AppAttributeSetter { AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES) : m_attribute(attribute), m_oldValue(QCoreApplication::testAttribute(attribute)) { QCoreApplication::setAttribute(attribute, useOpenGLES); } ~AppAttributeSetter() { QCoreApplication::setAttribute(m_attribute, m_oldValue); } private: Qt::ApplicationAttribute m_attribute; bool m_oldValue = false; }; struct SurfaceFormatSetter { SurfaceFormatSetter(const QSurfaceFormat &format) : m_oldFormat(QSurfaceFormat::defaultFormat()) { QSurfaceFormat::setDefaultFormat(format); } ~SurfaceFormatSetter() { QSurfaceFormat::setDefaultFormat(m_oldFormat); } private: QSurfaceFormat m_oldFormat; }; + +#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) +QString qEnvironmentVariable(const char *varName) { + return qgetenv(varName); +} +#endif + +struct EnvironmentSetter +{ + EnvironmentSetter(const QLatin1String &env, const QString &value) + : m_env(env) + { + if (qEnvironmentVariableIsEmpty(m_env.latin1())) { + m_oldValue = qgetenv(env.latin1()); + } + if (!value.isEmpty()) { + qputenv(env.latin1(), value.toLatin1()); + } else { + qunsetenv(env.latin1()); + } + } + + ~EnvironmentSetter() { + if (m_oldValue) { + qputenv(m_env.latin1(), (*m_oldValue).toLatin1()); + } else { + qunsetenv(m_env.latin1()); + } + } + +private: + const QLatin1String m_env; + boost::optional m_oldValue; +}; + } boost::optional -KisOpenGLModeProber::probeFormat(const QSurfaceFormat &format, bool adjustGlobalState) +KisOpenGLModeProber::probeFormat(const KisOpenGL::RendererConfig &rendererConfig, + bool adjustGlobalState) { + const QSurfaceFormat &format = rendererConfig.format; + QScopedPointer sharedContextSetter; QScopedPointer glSetter; QScopedPointer glesSetter; QScopedPointer formatSetter; + QScopedPointer rendererSetter; QScopedPointer application; int argc = 1; QByteArray probeAppName("krita"); char *argv = probeAppName.data(); if (adjustGlobalState) { sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false)); if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) { glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES)); glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES)); } + rendererSetter.reset(new EnvironmentSetter(QLatin1String("QT_ANGLE_PLATFORM"), angleRendererToString(rendererConfig.angleRenderer))); formatSetter.reset(new SurfaceFormatSetter(format)); QGuiApplication::setDesktopSettingsAware(false); application.reset(new QGuiApplication(argc, &argv)); QGuiApplication::setDesktopSettingsAware(true); } QWindow surface; surface.setFormat(format); surface.setSurfaceType(QSurface::OpenGLSurface); surface.create(); QOpenGLContext context; context.setFormat(format); if (!context.create()) { dbgOpenGL << "OpenGL context cannot be created"; return boost::none; } if (!context.isValid()) { dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status"; return boost::none; } if (!context.makeCurrent(&surface)) { dbgOpenGL << "OpenGL context cannot be made current"; return boost::none; } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) { dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace(); return boost::none; } #endif return Result(context); } bool KisOpenGLModeProber::fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs) { return lhs == rhs || ((lhs == KisSurfaceColorSpace::DefaultColorSpace || lhs == KisSurfaceColorSpace::sRGBColorSpace) && (rhs == KisSurfaceColorSpace::DefaultColorSpace || rhs == KisSurfaceColorSpace::sRGBColorSpace)); } void KisOpenGLModeProber::initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config, QSurfaceFormat *format) { #ifdef HAVE_HDR if (config == KisConfig::BT2020_PQ) { format->setRedBufferSize(10); format->setGreenBufferSize(10); format->setBlueBufferSize(10); format->setAlphaBufferSize(2); format->setColorSpace(KisSurfaceColorSpace::bt2020PQColorSpace); } else if (config == KisConfig::BT709_G10) { format->setRedBufferSize(16); format->setGreenBufferSize(16); format->setBlueBufferSize(16); format->setAlphaBufferSize(16); format->setColorSpace(KisSurfaceColorSpace::scRGBColorSpace); } else #else if (config == KisConfig::BT2020_PQ) { qWarning() << "WARNING: Bt.2020 PQ surface type is not supoprted by this build of Krita"; } else if (config == KisConfig::BT709_G10) { qWarning() << "WARNING: scRGB surface type is not supoprted by this build of Krita"; } #endif { format->setRedBufferSize(8); format->setGreenBufferSize(8); format->setBlueBufferSize(8); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) format->setAlphaBufferSize(8); #else format->setAlphaBufferSize(0); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // TODO: check if we can use real sRGB space here format->setColorSpace(KisSurfaceColorSpace::DefaultColorSpace); #endif } } bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format) { #ifdef HAVE_HDR bool isBt2020PQ = format.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace && format.redBufferSize() == 10 && format.greenBufferSize() == 10 && format.blueBufferSize() == 10 && format.alphaBufferSize() == 2; bool isBt709G10 = format.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace && format.redBufferSize() == 16 && format.greenBufferSize() == 16 && format.blueBufferSize() == 16 && format.alphaBufferSize() == 16; return isBt2020PQ || isBt709G10; #else Q_UNUSED(format); return false; #endif } +QString KisOpenGLModeProber::angleRendererToString(KisOpenGL::AngleRenderer renderer) +{ + QString value; + + switch (renderer) { + case KisOpenGL::AngleRendererDefault: + break; + case KisOpenGL::AngleRendererD3d9: + value = "d3d9"; + break; + case KisOpenGL::AngleRendererD3d11: + value = "d3d11"; + break; + case KisOpenGL::AngleRendererD3d11Warp: + value = "warp"; + break; + }; + + return value; +} + KisOpenGLModeProber::Result::Result(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_vendorString = QString(reinterpret_cast(funcs->glGetString(GL_VENDOR))); m_shadingLanguageString = QString(reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); m_format = context.format(); } diff --git a/libs/ui/opengl/KisOpenGLModeProber.h b/libs/ui/opengl/KisOpenGLModeProber.h index 9a2124d86c..d1aa59b30a 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.h +++ b/libs/ui/opengl/KisOpenGLModeProber.h @@ -1,146 +1,148 @@ /* * Copyright (c) 2017 Alvin Wong * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISOPENGLMODEPROBER_H #define KISOPENGLMODEPROBER_H #include "kritaui_export.h" #include "kis_config.h" #include #include "KisSurfaceColorSpace.h" #include +#include "kis_opengl.h" class KoColorProfile; class KRITAUI_EXPORT KisOpenGLModeProber { public: class Result; public: KisOpenGLModeProber(); ~KisOpenGLModeProber(); static KisOpenGLModeProber* instance(); bool useHDRMode() const; QSurfaceFormat surfaceformatInUse() const; const KoColorProfile *rootSurfaceColorProfile() const; - boost::optional probeFormat(const QSurfaceFormat &format, + boost::optional probeFormat(const KisOpenGL::RendererConfig &rendererConfig, bool adjustGlobalState = true); static bool fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs); + static QString angleRendererToString(KisOpenGL::AngleRenderer renderer); public: static void initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config, QSurfaceFormat *format); static bool isFormatHDR(const QSurfaceFormat &format); }; class KisOpenGLModeProber::Result { public: Result(QOpenGLContext &context); int glMajorVersion() const { return m_glMajorVersion; } int glMinorVersion() const { return m_glMinorVersion; } bool supportsDeprecatedFunctions() const { return m_supportsDeprecatedFunctions; } bool isOpenGLES() const { return m_isOpenGLES; } QString rendererString() const { return m_rendererString; } QString driverVersionString() const { return m_driverVersionString; } bool isSupportedVersion() const { return #ifdef Q_OS_MACOS ((m_glMajorVersion * 100 + m_glMinorVersion) >= 302) #else (m_glMajorVersion >= 3 && (m_supportsDeprecatedFunctions || m_isOpenGLES)) || ((m_glMajorVersion * 100 + m_glMinorVersion) == 201) #endif ; } bool supportsLoD() const { return (m_glMajorVersion * 100 + m_glMinorVersion) >= 300; } bool hasOpenGL3() const { return (m_glMajorVersion * 100 + m_glMinorVersion) >= 302; } bool supportsFenceSync() const { return m_glMajorVersion >= 3; } #ifdef Q_OS_WIN // This is only for detecting whether ANGLE is being used. // For detecting generic OpenGL ES please check isOpenGLES bool isUsingAngle() const { return m_rendererString.startsWith("ANGLE", Qt::CaseInsensitive); } #endif QString shadingLanguageString() const { return m_shadingLanguageString; } QString vendorString() const { return m_vendorString; } QSurfaceFormat format() const { return m_format; } private: int m_glMajorVersion = 0; int m_glMinorVersion = 0; bool m_supportsDeprecatedFunctions = false; bool m_isOpenGLES = false; QString m_rendererString; QString m_driverVersionString; QString m_vendorString; QString m_shadingLanguageString; QSurfaceFormat m_format; }; #endif // KISOPENGLMODEPROBER_H diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index b979b84b90..6aa26c51ad 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,775 +1,912 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #include #include "kis_assert.h" #include #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { // config option, set manually by main() bool g_isDebugSynchronous = false; bool g_sanityDefaultFormatIsSet = false; boost::optional openGLCheckResult; bool g_needsFenceWorkaround = false; bool g_needsPixmapCacheWorkaround = false; QString g_surfaceFormatDetectionLog; QString g_debugText("OpenGL Info\n **OpenGL not initialized**"); QVector g_openglWarningStrings; KisOpenGL::OpenGLRenderers g_supportedRenderers; KisOpenGL::OpenGLRenderer g_rendererPreferredByQt; void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) { g_supportedRenderers = supportedRenderers; g_rendererPreferredByQt = preferredByQt; } void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } + + KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) { + + KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL; + + if (info.isOpenGLES()) { + const QString rendererString = info.rendererString().toLower(); + + if (rendererString.contains("basic render driver") || + rendererString.contains("software")) { + + result = KisOpenGL::RendererSoftware; + } else { + result = KisOpenGL::RendererOpenGLES; + } + } + + return result; + } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { g_openglWarningStrings << warning; } void KisOpenGLPrivate::overrideOpenGLWarningString(QVector warnings) { g_openglWarningStrings = warnings; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet); + KisOpenGL::RendererConfig config; + config.format = QSurfaceFormat::defaultFormat(); + openGLCheckResult = - KisOpenGLModeProber::instance()->probeFormat(QSurfaceFormat::defaultFormat(), false); + KisOpenGLModeProber::instance()->probeFormat(config, false); g_debugText.clear(); QDebug debugOut(&g_debugText); debugOut << "OpenGL Info\n"; - debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); - debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); - debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); - debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); - debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); - debugOut << "\n Current format: " << openGLCheckResult->format(); - debugOut.nospace(); - debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); - debugOut.resetFormat(); - debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); - debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); + + if (openGLCheckResult) { + debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); + debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); + debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); + debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); + debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); + debugOut << "\n Current format: " << openGLCheckResult->format(); + debugOut.nospace(); + debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); + debugOut.resetFormat(); + debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); + debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); + } debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL); #ifdef Q_OS_WIN debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #else debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #endif debugOut << "\n== log ==\n"; debugOut.noquote(); debugOut << g_surfaceFormatDetectionLog; debugOut.resetFormat(); debugOut << "\n== end log =="; dbgOpenGL.noquote().nospace() << g_debugText; KisUsageLogger::write(g_debugText); + if (!openGLCheckResult) { + return; + } + + // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif KisConfig cfg(true); if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { g_needsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (openGLCheckResult->vendorString().toUpper().contains("NVIDIA")) { g_needsPixmapCacheWorkaround = true; const QRect screenSize = QApplication::desktop()->screenGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg(true); initialize(); const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(openGLCheckResult->rendererString().toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); } const QString &KisOpenGL::getDebugText() { initialize(); return g_debugText; } QStringList KisOpenGL::getOpenGLWarnings() { QStringList strings; Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) { strings << item.toString(); } return strings; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); - return openGLCheckResult->supportsLoD(); + return openGLCheckResult && openGLCheckResult->supportsLoD(); } bool KisOpenGL::hasOpenGL3() { initialize(); - return openGLCheckResult->hasOpenGL3(); + return openGLCheckResult && openGLCheckResult->hasOpenGL3(); } bool KisOpenGL::hasOpenGLES() { initialize(); - return openGLCheckResult->isOpenGLES(); + return openGLCheckResult && openGLCheckResult->isOpenGLES(); } bool KisOpenGL::supportsFenceSync() { initialize(); - return openGLCheckResult->supportsFenceSync(); + return openGLCheckResult && openGLCheckResult->supportsFenceSync(); } bool KisOpenGL::needsFenceWorkaround() { initialize(); return g_needsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return g_needsPixmapCacheWorkaround; } void KisOpenGL::testingInitializeDefaultSurfaceFormat() { - setDefaultSurfaceFormat(selectSurfaceFormat(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); + setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); } void KisOpenGL::setDebugSynchronous(bool value) { g_isDebugSynchronous = value; } KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer() { - const QSurfaceFormat::RenderableType renderer = QSurfaceFormat::defaultFormat().renderableType(); - - return renderer == QSurfaceFormat::OpenGLES ? RendererOpenGLES : - renderer == QSurfaceFormat::OpenGL ? RendererDesktopGL : - RendererAuto; + if (!openGLCheckResult) return RendererAuto; + return getRendererFromProbeResult(*openGLCheckResult); } KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer() { return g_rendererPreferredByQt; } KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers() { return g_supportedRenderers; } KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString()); } void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer) { switch (renderer) { + case RendererNone: + return QStringLiteral("none"); + case RendererSoftware: + return QStringLiteral("software"); case RendererDesktopGL: return QStringLiteral("desktop"); case RendererOpenGLES: return QStringLiteral("angle"); default: return QStringLiteral("auto"); } } KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer) { if (renderer == "desktop") { return RendererDesktopGL; } else if (renderer == "angle") { return RendererOpenGLES; + } else if (renderer == "software") { + return RendererSoftware; + } else if (renderer == "none") { + return RendererNone; } else { return RendererAuto; } } +KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const +{ + KisOpenGL::OpenGLRenderer result = RendererAuto; + + if (format.renderableType() == QSurfaceFormat::OpenGLES && + angleRenderer == AngleRendererD3d11Warp) { + + result = RendererSoftware; + + } else if (format.renderableType() == QSurfaceFormat::OpenGLES && + angleRenderer == AngleRendererD3d11) { + + result = RendererOpenGLES; + } else if (format.renderableType() == QSurfaceFormat::OpenGL) { + result = RendererDesktopGL; + } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType && + angleRenderer == AngleRendererD3d11) { + // noop + } else { + qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer); + } + + return result; +} + namespace { -QSurfaceFormat generateSurfaceFormat(QSurfaceFormat::RenderableType renderer, - KisConfig::RootSurfaceFormat rootSurfaceFormat, - bool debugContext) +typedef std::pair RendererInfo; + +RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer) { - QSurfaceFormat format; + RendererInfo info = {QSurfaceFormat::DefaultRenderableType, + KisOpenGL::AngleRendererD3d11}; + + switch (renderer) { + case KisOpenGL::RendererNone: + info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault}; + break; + case KisOpenGL::RendererAuto: + break; + case KisOpenGL::RendererDesktopGL: + info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11}; + break; + case KisOpenGL::RendererOpenGLES: + info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11}; + break; + case KisOpenGL::RendererSoftware: + info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp}; + break; + } + + return info; +} + + +KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer, + KisConfig::RootSurfaceFormat rootSurfaceFormat, + bool debugContext) +{ + RendererInfo info = getRendererInfo(renderer); + + KisOpenGL::RendererConfig config; + config.angleRenderer = info.second; + + QSurfaceFormat &format = config.format; #ifdef Q_OS_MACOS format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #else // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format); - format.setRenderableType(renderer); + format.setRenderableType(info.first); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing if (debugContext) { format.setOption(QSurfaceFormat::DebugContext, true); } - return format; + return config; } bool isOpenGLRendererBlacklisted(const QString &rendererString, const QString &driverVersionString, QVector *warningMessage) { bool isBlacklisted = false; #ifndef Q_OS_WIN Q_UNUSED(rendererString); Q_UNUSED(driverVersionString); Q_UNUSED(warningMessage); #else // Special blacklisting of OpenGL/ANGLE is tracked on: // https://phabricator.kde.org/T7411 // HACK: Specifically detect for Intel driver build number // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html if (rendererString.startsWith("Intel")) { KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL."); KLocalizedString grossIntelWarning = ki18n( "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. " "You may manually switch to OpenGL but it is not guaranteed to work properly." ); QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b"); QRegularExpressionMatch match = regex.match(driverVersionString); if (match.hasMatch()) { int driverBuild = match.captured(1).toInt(); if (driverBuild > 4636 && driverBuild < 4729) { // Make ANGLE the preferred renderer for Intel driver versions // between build 4636 and 4729 (exclusive) due to an UI offset bug. // See https://communities.intel.com/thread/116003 // (Build 4636 is known to work from some test results) qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else if (driverBuild == 4358) { // There are several reports on a bug where the canvas is not being // updated properly which has debug info pointing to this build. qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else { // Intel tends to randomly break OpenGL in some of their new driver // builds, therefore we just shouldn't use OpenGL by default to // reduce bug report noises. qDebug() << "Detected Intel driver, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } else { // In case Intel changed the driver version format to something that // we don't understand, we still select ANGLE. qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } #endif return isBlacklisted; } boost::optional orderPreference(bool lhs, bool rhs) { if (lhs == rhs) return boost::none; if (lhs && !rhs) return true; if (!lhs && rhs) return false; return false; } #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; } class FormatPositionLess { public: FormatPositionLess() { } - bool operator()(const QSurfaceFormat &lhs, const QSurfaceFormat &rhs) const { + bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const { KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace); + if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) { + ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId())); + } + #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - ORDER_BY(isPreferredColorSpace(lhs.colorSpace()), - isPreferredColorSpace(rhs.colorSpace())); + ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()), + isPreferredColorSpace(rhs.format.colorSpace())); #endif - if (doPreferHDR()) { - ORDER_BY(isHDRFormat(lhs), isHDRFormat(rhs)); + ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format)); } else { - ORDER_BY(!isHDRFormat(lhs), !isHDRFormat(rhs)); + ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format)); } - if (m_preferredRendererByUser != QSurfaceFormat::DefaultRenderableType) { - ORDER_BY(lhs.renderableType() == m_preferredRendererByUser, - rhs.renderableType() == m_preferredRendererByUser); + if (m_preferredRendererByUser != KisOpenGL::RendererAuto) { + ORDER_BY(lhs.rendererId() == m_preferredRendererByUser, + rhs.rendererId() == m_preferredRendererByUser); } - ORDER_BY(!isBlacklisted(lhs), !isBlacklisted(rhs)); + ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId())); if (doPreferHDR() && - m_preferredRendererByHDR != QSurfaceFormat::DefaultRenderableType) { + m_preferredRendererByHDR != KisOpenGL::RendererAuto) { - ORDER_BY(lhs.renderableType() == m_preferredRendererByHDR, - rhs.renderableType() == m_preferredRendererByHDR); + ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR, + rhs.rendererId() == m_preferredRendererByHDR); } - KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != QSurfaceFormat::DefaultRenderableType); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto); - ORDER_BY(lhs.renderableType() == m_preferredRendererByQt, - rhs.renderableType() == m_preferredRendererByQt); + ORDER_BY(lhs.rendererId() == m_preferredRendererByQt, + rhs.rendererId() == m_preferredRendererByQt); return false; } public: void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) { m_preferredColorSpace = preferredColorSpace; } - void setPreferredRendererByQt(const QSurfaceFormat::RenderableType &preferredRendererByQt) { + void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) { m_preferredRendererByQt = preferredRendererByQt; } - void setPreferredRendererByUser(const QSurfaceFormat::RenderableType &preferredRendererByUser) { + void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) { m_preferredRendererByUser = preferredRendererByUser; } - void setPreferredRendererByHDR(const QSurfaceFormat::RenderableType &preferredRendererByHDR) { + void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) { m_preferredRendererByHDR = preferredRendererByHDR; } void setOpenGLBlacklisted(bool openGLBlacklisted) { m_openGLBlacklisted = openGLBlacklisted; } void setOpenGLESBlacklisted(bool openGLESBlacklisted) { m_openGLESBlacklisted = openGLESBlacklisted; } bool isOpenGLBlacklisted() const { return m_openGLBlacklisted; } bool isOpenGLESBlacklisted() const { return m_openGLESBlacklisted; } KisSurfaceColorSpace preferredColorSpace() const { return m_preferredColorSpace; } - QSurfaceFormat::RenderableType preferredRendererByUser() const { + KisOpenGL::OpenGLRenderer preferredRendererByUser() const { return m_preferredRendererByUser; } private: bool isHDRFormat(const QSurfaceFormat &f) const { #ifdef HAVE_HDR return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace || f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace; #else Q_UNUSED(f); return false; #endif } - bool isBlacklisted(const QSurfaceFormat &f) const { - KIS_SAFE_ASSERT_RECOVER_NOOP(f.renderableType() == QSurfaceFormat::OpenGL || - f.renderableType() == QSurfaceFormat::OpenGLES); + bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const { + return r == KisOpenGL::RendererSoftware; + } - return (f.renderableType() == QSurfaceFormat::OpenGL && m_openGLBlacklisted) || - (f.renderableType() == QSurfaceFormat::OpenGLES && m_openGLESBlacklisted); + bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const { + KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto || + r == KisOpenGL::RendererDesktopGL || + r == KisOpenGL::RendererOpenGLES || + r == KisOpenGL::RendererSoftware || + r == KisOpenGL::RendererNone); + + return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) || + (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) || + (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted); } bool doPreferHDR() const { #ifdef HAVE_HDR return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace || m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace; #else return false; #endif } bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const { return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs); return false; } private: KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace; - QSurfaceFormat::RenderableType m_preferredRendererByQt = QSurfaceFormat::OpenGL; - QSurfaceFormat::RenderableType m_preferredRendererByUser = QSurfaceFormat::DefaultRenderableType; - QSurfaceFormat::RenderableType m_preferredRendererByHDR = QSurfaceFormat::DefaultRenderableType; + KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL; + KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto; + KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto; bool m_openGLBlacklisted = false; bool m_openGLESBlacklisted = false; }; struct DetectionDebug : public QDebug { DetectionDebug(QString *string) : QDebug(string), m_string(string), m_originalSize(string->size()) {} ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; } QString *m_string; int m_originalSize; }; } #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog) -QSurfaceFormat KisOpenGL::selectSurfaceFormat(KisOpenGL::OpenGLRenderer preferredRenderer, - KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, - bool enableDebug) +KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, + KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, + bool enableDebug) { QVector warningMessages; using Info = boost::optional; - QVector renderers({QSurfaceFormat::OpenGLES, QSurfaceFormat::OpenGL}); + QHash renderersToTest; + renderersToTest.insert(RendererDesktopGL, Info()); + renderersToTest.insert(RendererOpenGLES, Info()); + +#ifdef Q_OS_WIN + renderersToTest.insert(RendererSoftware, Info()); +#endif + #ifdef HAVE_HDR QVector formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ}); #else QVector formatSymbols({KisConfig::BT709_G22}); #endif - QVector preferredFormats; - Q_FOREACH (const QSurfaceFormat::RenderableType renderer, renderers) { - Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { - preferredFormats << generateSurfaceFormat(renderer, formatSymbol, enableDebug); + KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto, + KisConfig::BT709_G22, false); + Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); + +#ifdef Q_OS_WIN + if (!info) { + // try software rasterizer (WARP) + defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware, + KisConfig::BT709_G22, false); + info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); + + if (!info) { + renderersToTest.remove(RendererSoftware); } } +#endif - QSurfaceFormat defaultFormat = generateSurfaceFormat(QSurfaceFormat::DefaultRenderableType, - KisConfig::BT709_G22, false); - Info info = KisOpenGLModeProber::instance()->probeFormat(defaultFormat); + if (!info) return KisOpenGL::RendererConfig(); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(info, QSurfaceFormat()); + const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info); + + OpenGLRenderers supportedRenderers = RendererNone; FormatPositionLess compareOp; - compareOp.setPreferredRendererByQt(info->isOpenGLES() ? QSurfaceFormat::OpenGLES : QSurfaceFormat::OpenGL); + compareOp.setPreferredRendererByQt(defaultRenderer); #ifdef HAVE_HDR compareOp.setPreferredColorSpace( preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace : preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace : KisSurfaceColorSpace::bt2020PQColorSpace); #else Q_UNUSED(preferredRootSurfaceFormat); compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace); #endif #ifdef Q_OS_WIN - compareOp.setPreferredRendererByHDR(QSurfaceFormat::OpenGLES); + compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES); #endif - compareOp.setPreferredRendererByUser(preferredRenderer == KisOpenGL::RendererDesktopGL ? QSurfaceFormat::OpenGL : - preferredRenderer == KisOpenGL::RendererOpenGLES ? QSurfaceFormat::OpenGLES : - QSurfaceFormat::DefaultRenderableType); + compareOp.setPreferredRendererByUser(preferredRenderer); compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm - OpenGLRenderers supportedRenderers = RendererNone; - OpenGLRenderer preferredByQt = info->isOpenGLES() ? RendererOpenGLES : RendererDesktopGL; - if (!info->isOpenGLES()) { - compareOp.setOpenGLBlacklisted(isOpenGLRendererBlacklisted(info->rendererString(), - info->driverVersionString(), - &warningMessages)); + renderersToTest[defaultRenderer] = info; - supportedRenderers |= RendererDesktopGL; + for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { + Info info = it.value(); - info = KisOpenGLModeProber::instance()-> - probeFormat(generateSurfaceFormat(QSurfaceFormat::OpenGLES, - KisConfig::BT709_G22, false)); - if (info) { - supportedRenderers |= RendererOpenGLES; + if (!info) { + info = KisOpenGLModeProber::instance()-> + probeFormat(generateSurfaceConfig(it.key(), + KisConfig::BT709_G22, false)); + *it = info; } - } else { - supportedRenderers |= RendererOpenGLES; - - info = KisOpenGLModeProber::instance()-> - probeFormat(generateSurfaceFormat(QSurfaceFormat::OpenGL, - KisConfig::BT709_G22, false)); - if (!info || info->isOpenGLES()) { - compareOp.setOpenGLBlacklisted(true); - } else { - compareOp.setOpenGLBlacklisted(isOpenGLRendererBlacklisted(info->rendererString(), - info->driverVersionString(), - &warningMessages)); + compareOp.setOpenGLBlacklisted( + !info || + isOpenGLRendererBlacklisted(info->rendererString(), + info->driverVersionString(), + &warningMessages)); - supportedRenderers |= RendererDesktopGL; + if (info && info->isSupportedVersion()) { + supportedRenderers |= it.key(); } } + OpenGLRenderer preferredByQt = defaultRenderer; + if (preferredByQt == RendererDesktopGL && supportedRenderers & RendererDesktopGL && compareOp.isOpenGLBlacklisted()) { preferredByQt = RendererOpenGLES; } else if (preferredByQt == RendererOpenGLES && supportedRenderers & RendererOpenGLES && compareOp.isOpenGLESBlacklisted()) { preferredByQt = RendererDesktopGL; } + QVector preferredConfigs; + for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { + // if default mode of the renderer doesn't work, then custom won't either + if (!it.value()) continue; - std::stable_sort(preferredFormats.begin(), preferredFormats.end(), compareOp); + Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { + preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug); + } + } + + std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp); dbgDetection() << "Supported renderers:" << supportedRenderers; dbgDetection() << "Surface format preference list:"; - Q_FOREACH (const QSurfaceFormat &format, preferredFormats) { - dbgDetection() << "*" << format; - dbgDetection() << " " << format.renderableType(); + Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { + dbgDetection() << "*" << config.format; + dbgDetection() << " " << config.rendererId(); } - QSurfaceFormat resultFormat = defaultFormat; + KisOpenGL::RendererConfig resultConfig = defaultConfig; - Q_FOREACH (const QSurfaceFormat &format, preferredFormats) { + if (preferredRenderer != RendererNone) { + Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - dbgDetection() <<"Probing format..." << format.colorSpace() << format.renderableType(); + dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId(); #else - dbgDetection() <<"Probing format..." << format.renderableType(); + dbgDetection() <<"Probing format..." << config.rendererId(); #endif - Info info = KisOpenGLModeProber::instance()->probeFormat(format); + Info info = KisOpenGLModeProber::instance()->probeFormat(config); - if (info && info->isSupportedVersion()) { + if (info && info->isSupportedVersion()) { #ifdef Q_OS_WIN - // HACK: Block ANGLE with Direct3D9 - // Direct3D9 does not give OpenGL ES 3.0 - // Some versions of ANGLE returns OpenGL version 3.0 incorrectly + // HACK: Block ANGLE with Direct3D9 + // Direct3D9 does not give OpenGL ES 3.0 + // Some versions of ANGLE returns OpenGL version 3.0 incorrectly - if (info->isUsingAngle() && - info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { + if (info->isUsingAngle() && + info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { - dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; + dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; - continue; - } + continue; + } #endif - dbgDetection() << "Found format:" << format; - dbgDetection() << " " << format.renderableType(); + dbgDetection() << "Found format:" << config.format; + dbgDetection() << " " << config.rendererId(); - resultFormat = format; - break; + resultConfig = config; + break; + } } - } - { - const bool colorSpaceIsCorrect = -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), - resultFormat.colorSpace()); + { + const bool colorSpaceIsCorrect = + #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), + resultConfig.format.colorSpace()); #else - true; + true; #endif - const bool rendererIsCorrect = - compareOp.preferredRendererByUser() == QSurfaceFormat::DefaultRenderableType || - compareOp.preferredRendererByUser() == resultFormat.renderableType(); + const bool rendererIsCorrect = + compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto || + compareOp.preferredRendererByUser() == resultConfig.rendererId(); - if (!rendererIsCorrect && colorSpaceIsCorrect) { - warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); - } else if (!colorSpaceIsCorrect) { - warningMessages << ki18n("Preferred output format is not supported by available renderers"); - } + if (!rendererIsCorrect && colorSpaceIsCorrect) { + warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); + } else if (!colorSpaceIsCorrect) { + warningMessages << ki18n("Preferred output format is not supported by available renderers"); + } + } + } else { + resultConfig.format = QSurfaceFormat(); + resultConfig.angleRenderer = AngleRendererDefault; } overrideSupportedRenderers(supportedRenderers, preferredByQt); overrideOpenGLWarningString(warningMessages); - return resultFormat; + return resultConfig; } -void KisOpenGL::setDefaultSurfaceFormat(const QSurfaceFormat &format) +void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config) { KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet); g_sanityDefaultFormatIsSet = true; - QSurfaceFormat::setDefaultFormat(format); + QSurfaceFormat::setDefaultFormat(config.format); + +#ifdef Q_OS_WIN + // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP + // might get weird crashes atm. + qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1()); +#endif + + if (config.format.renderableType() == QSurfaceFormat::OpenGLES) { + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); + } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) { + QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); + } } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/libs/ui/opengl/kis_opengl.h b/libs/ui/opengl/kis_opengl.h index fa4fcfa90b..365003a93b 100644 --- a/libs/ui/opengl/kis_opengl.h +++ b/libs/ui/opengl/kis_opengl.h @@ -1,122 +1,137 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_OPENGL_H_ #define KIS_OPENGL_H_ /** @file */ #include #include #include #include "kis_config.h" #include "kritaui_export.h" class QOpenGLContext; class QString; class QStringList; class QSurfaceFormat; /** * This class manages a shared OpenGL context and provides utility * functions for checking capabilities and error reporting. */ class KRITAUI_EXPORT KisOpenGL { public: enum FilterMode { NearestFilterMode, // nearest BilinearFilterMode, // linear, no mipmap TrilinearFilterMode, // LINEAR_MIPMAP_LINEAR HighQualityFiltering // Mipmaps + custom shader }; -public: enum OpenGLRenderer { RendererNone = 0x00, RendererAuto = 0x01, RendererDesktopGL = 0x02, RendererOpenGLES = 0x04, + RendererSoftware = 0x08 + }; + Q_DECLARE_FLAGS(OpenGLRenderers, OpenGLRenderer) + + enum AngleRenderer { + AngleRendererDefault = 0x0000, + AngleRendererD3d11 = 0x0002, + AngleRendererD3d9 = 0x0004, + AngleRendererD3d11Warp = 0x0008, // "Windows Advanced Rasterization Platform" }; - Q_DECLARE_FLAGS(OpenGLRenderers, OpenGLRenderer); - static QSurfaceFormat selectSurfaceFormat(KisOpenGL::OpenGLRenderer preferredRenderer, + struct RendererConfig { + QSurfaceFormat format; + AngleRenderer angleRenderer = AngleRendererDefault; + + OpenGLRenderer rendererId() const; + }; + +public: + static RendererConfig selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug); - static void setDefaultSurfaceFormat(const QSurfaceFormat &format); + static void setDefaultSurfaceConfig(const RendererConfig &config); static OpenGLRenderer getCurrentOpenGLRenderer(); static OpenGLRenderer getQtPreferredOpenGLRenderer(); static OpenGLRenderers getSupportedOpenGLRenderers(); static OpenGLRenderer getUserPreferredOpenGLRendererConfig(); static void setUserPreferredOpenGLRendererConfig(OpenGLRenderer renderer); static QString convertOpenGLRendererToConfig(OpenGLRenderer renderer); static OpenGLRenderer convertConfigToOpenGLRenderer(QString renderer); /// Request OpenGL version 3.2 static void initialize(); /// Initialize shared OpenGL context static void initializeContext(QOpenGLContext *ctx); static const QString &getDebugText(); static QStringList getOpenGLWarnings(); static bool supportsLoD(); static bool hasOpenGL3(); static bool hasOpenGLES(); /// Check for OpenGL static bool hasOpenGL(); /** * @brief supportsFilter * @return True if OpenGL provides fence sync methods. */ static bool supportsFenceSync(); /** * Returns true if we have a driver that has bugged support to sync objects (a fence) * and false otherwise. */ static bool needsFenceWorkaround(); /** * @see a comment in initializeContext() */ static bool needsPixmapCacheWorkaround(); static void testingInitializeDefaultSurfaceFormat(); static void setDebugSynchronous(bool value); private: static void fakeInitWindowsOpenGL(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt); KisOpenGL(); }; #ifdef Q_OS_WIN Q_DECLARE_OPERATORS_FOR_FLAGS(KisOpenGL::OpenGLRenderers); #endif #endif // KIS_OPENGL_H_ diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index c34b4c53ce..9401b990bc 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1035 +1,1035 @@ /* 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 #include "KisOpenGLModeProber.h" #include #ifndef Q_OS_MACOS #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_MACOS QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; QColor cursorColor; bool lodSwitchInProgress = false; 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(false); cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_MACOS setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_DontCreateNativeAncestors, true); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // we should make sure the texture doesn't have alpha channel, // otherwise blending will not work correctly. if (KisOpenGLModeProber::instance()->useHDRMode()) { setTextureFormat(GL_RGBA16F); } else { /** * When in pure OpenGL mode, the canvas surface will have alpha * channel. Therefore, if our canvas blending algorithm produces * semi-transparent pixels (and it does), then Krita window itself * will become transparent. Which is not good. * * In Angle mode, GL_RGB8 is not available (and the transparence effect * doesn't exist at all). */ if (!KisOpenGL::hasOpenGLES()) { setTextureFormat(GL_RGB8); } } #endif setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged())); slotConfigChanged(); slotPixelGridModeChanged(); 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::notifyImageColorSpaceChanged(const KoColorSpace *cs) { // FIXME: on color space change the data is refetched multiple // times by different actors! if (d->openGLImageTextures->setImageColorSpace(cs)) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } } 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_MACOS 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(true); 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(false); qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context), QMessageBox::Close); - cfg.setUseOpenGL(false); + cfg.disableOpenGL(); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int /*width*/, int /*height*/) { // The given size is the widget size but here we actually want to give // KisCoordinatesConverter the viewport size aligned to device pixels. coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); 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(false); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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_MACOS 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); } d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), 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::setLodResetInProgress(bool value) { d->lodSwitchInProgress = value; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); viewportRect &= vrect; } 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(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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, widgetSize.width(), widgetSize.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(); QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.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, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); if (!renderingLimit.isEmpty()) { widgetRectInImagePixels &= renderingLimit; } qreal scaleX, scaleY; converter->imagePhysicalScale(&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; QRectF modelRect; if (renderingLimit.isEmpty()) { textureRect = tile->tileRectInTexturePixels(); modelRect = tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } else { const QRect limitedTileRect = tile->tileRectInImagePixels() & renderingLimit; textureRect = tile->imageRectInTexturePixels(limitedTileRect); modelRect = limitedTileRect.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); } glActiveTexture(GL_TEXTURE0); const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } 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); } QSize KisOpenGLCanvas2::viewportDevicePixelSize() const { // This is how QOpenGLCanvas sets the FBO and the viewport size. If // devicePixelRatioF() is non-integral, the result is truncated. int viewportWidth = static_cast(width() * devicePixelRatioF()); int viewportHeight = static_cast(height() * devicePixelRatioF()); return QSize(viewportWidth, viewportHeight); } QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const { QSize viewportSize = viewportDevicePixelSize(); qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); return QSizeF(scaledWidth, scaledHeight); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true); 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->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } void KisOpenGLCanvas2::slotPixelGridModeChanged() { KisConfig cfg(true); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); update(); } 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(); KoColor convertedBackgroudColor = canvas()->displayColorConverter()->applyDisplayFiltering( KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()), Float32BitsColorDepthID); const float *pixel = reinterpret_cast(convertedBackgroudColor.data()); glClearColor(pixel[0], pixel[1], pixel[2], 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::setDisplayColorConverter(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(), 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, d->lodSwitchInProgress); } return QRect(); // FIXME: Implement dirty rect for OpenGL } QVector KisOpenGLCanvas2::updateCanvasProjection(const QVector &infoObjects) { #ifdef Q_OS_MACOS /** * On OSX openGL defferent (shared) contexts have different execution queues. * It means that the textures uploading and their painting can be easily reordered. * To overcome the issue, we should ensure that the textures are uploaded in the * same openGL context as the painting is done. */ QOpenGLContext *oldContext = QOpenGLContext::currentContext(); QSurface *oldSurface = oldContext ? oldContext->surface() : 0; this->makeCurrent(); #endif QVector result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects); #ifdef Q_OS_MACOS if (oldContext) { oldContext->makeCurrent(oldSurface); } else { this->doneCurrent(); } #endif return result; } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index a800340260..99d892a829 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,183 +1,183 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) -include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata +include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp kis_file_layer_test.cpp kis_multinode_property_test.cpp KisFrameSerializerTest.cpp KisFrameCacheStoreTest.cpp kis_animation_exporter_test.cpp kis_prescaled_projection_test.cpp kis_asl_layer_style_serializer_test.cpp kis_animation_importer_test.cpp KisSpinBoxSplineUnitConverterTest.cpp KisDocumentReplaceTest.cpp LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-" ) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME KisSelectionDecorationTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeDummiesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeShapesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisModelIndexConverterTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME KisCategorizedListModelTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeJugglerCompressedTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisInputManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME kis_shape_controller_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME KisExiv2Test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME KisClipboardTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeBenchmark LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME KisPaintOnTransparencyMaskTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FillProcessingVisitorTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME FilterStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME KisSelectionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME KisNodeManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisDummiesFacadeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisZoomAndPanTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME KisActionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME KisCategoriesMapperTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp TEST_NAME kis_animation_frame_cache_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_derived_resources_test.cpp TEST_NAME kis_derived_resources_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") diff --git a/libs/ui/tests/KisDocumentReplaceTest.cpp b/libs/ui/tests/KisDocumentReplaceTest.cpp index c3ef8e9ae6..7513da616d 100644 --- a/libs/ui/tests/KisDocumentReplaceTest.cpp +++ b/libs/ui/tests/KisDocumentReplaceTest.cpp @@ -1,65 +1,65 @@ /* * Copyright (c) 2019 Tusooa Zhu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisDocumentReplaceTest.h" #include #include #include #include #include #include - +#include #include void KisDocumentReplaceTest::init() { m_doc = KisPart::instance()->createDocument(); qDebug() << m_doc->newImage("test", 512, 512, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", 0), KoColor(), KisConfig::RASTER_LAYER, 1, "", 96); } void KisDocumentReplaceTest::finalize() { delete m_doc; m_doc = 0; } void KisDocumentReplaceTest::testCopyFromDocument() { init(); QScopedPointer clonedDoc(m_doc->lockAndCreateSnapshot()); KisDocument *anotherDoc = KisPart::instance()->createDocument(); anotherDoc->newImage("test", 512, 512, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", 0), KoColor(), KisConfig::RASTER_LAYER, 2, "", 96); KisImageSP anotherImage(anotherDoc->image()); KisNodeSP root(anotherImage->rootLayer()); anotherDoc->copyFromDocument(*(clonedDoc.data())); // image pointer should not change QCOMPARE(anotherImage.data(), anotherDoc->image().data()); // root node should change QVERIFY(root.data() != anotherDoc->image()->rootLayer().data()); // node count should be the same QList oldNodes, newNodes; KisLayerUtils::recursiveApplyNodes(clonedDoc->image()->root(), [&oldNodes](KisNodeSP node) { oldNodes << node; }); KisLayerUtils::recursiveApplyNodes(anotherDoc->image()->root(), [&newNodes](KisNodeSP node) { newNodes << node; }); QCOMPARE(oldNodes.size(), newNodes.size()); KisPart::instance()->removeDocument(anotherDoc); finalize(); } -QTEST_MAIN(KisDocumentReplaceTest) +KISTEST_MAIN(KisDocumentReplaceTest) diff --git a/libs/ui/tests/kis_derived_resources_test.cpp b/libs/ui/tests/kis_derived_resources_test.cpp index c6bd59d4f9..75ca6abf65 100644 --- a/libs/ui/tests/kis_derived_resources_test.cpp +++ b/libs/ui/tests/kis_derived_resources_test.cpp @@ -1,159 +1,159 @@ /* * 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_derived_resources_test.h" #include #include #include #include #include "kis_canvas_resource_provider.h" #include #include #include #include #include #include #include #include #include #include "testutil.h" #include "opengl/kis_opengl.h" void addResourceTypes() { // All Krita's resource types KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KisOpenGL::testingInitializeDefaultSurfaceFormat(); KisConfig cfg(false); - cfg.setUseOpenGL(false); + cfg.disableOpenGL(); } void KisDerivedResourcesTest::test() { addResourceTypes(); KisDocument* doc = createEmptyDocument(); KisMainWindow* mainWindow = KisPart::instance()->createMainWindow(); QPointer view = new KisView(doc, mainWindow->resourceManager(), mainWindow->actionCollection(), mainWindow); KisViewManager *viewManager = new KisViewManager(mainWindow, mainWindow->actionCollection()); KoCanvasResourceProvider *manager = viewManager->canvasResourceProvider()->resourceManager(); QApplication::processEvents(); QString presetFileName = "autobrush_300px.kpp"; QVariant i; KisPaintOpPresetSP preset; if (!presetFileName.isEmpty()) { QString fullFileName = TestUtil::fetchDataFileLazy(presetFileName); preset = new KisPaintOpPreset(fullFileName); bool presetValid = preset->load(); Q_ASSERT(presetValid); Q_UNUSED(presetValid); i.setValue(preset); } QVERIFY(i.isValid()); QSignalSpy spy(manager, SIGNAL(canvasResourceChanged(int,QVariant))); manager->setResource(KisCanvasResourceProvider::CurrentPaintOpPreset, i); QMap expectedSignals; expectedSignals[KisCanvasResourceProvider::CurrentPaintOpPreset] = QVariant::fromValue(preset); expectedSignals[KisCanvasResourceProvider::EraserMode] = false; expectedSignals[KisCanvasResourceProvider::LodSizeThresholdSupported] = true; expectedSignals[KisCanvasResourceProvider::EffectiveLodAvailablility] = true; expectedSignals[KisCanvasResourceProvider::LodSizeThreshold] = 100; expectedSignals[KisCanvasResourceProvider::LodAvailability] = true; expectedSignals[KisCanvasResourceProvider::Opacity] = 1.0; expectedSignals[KisCanvasResourceProvider::Size] = 300.0; expectedSignals[KisCanvasResourceProvider::Flow] = 1.0; expectedSignals[KisCanvasResourceProvider::CurrentEffectiveCompositeOp] = COMPOSITE_OVER; expectedSignals[KisCanvasResourceProvider::CurrentCompositeOp] = COMPOSITE_OVER; auto it = spy.begin(); for (; it != spy.end(); ++it) { const int id = (*it)[0].toInt(); const QVariant value = (*it)[1]; if (!expectedSignals.contains(id)) { qDebug() << ppVar(id) << ppVar(value); QFAIL("Unexpected signal!"); } else { if (expectedSignals[id] != value) { qDebug() << ppVar(id) << ppVar(value) << ppVar(expectedSignals[id]); QFAIL("Unexpected value!"); } } } QCOMPARE(spy.size(), expectedSignals.size()); spy.clear(); preset->settings()->setPaintOpOpacity(0.8); QCOMPARE(spy.size(), 1); QCOMPARE(spy[0][0].toInt(), (int)KisCanvasResourceProvider::Opacity); QCOMPARE(spy[0][1].toDouble(), 0.8); spy.clear(); mainWindow->hide(); QApplication::processEvents(); delete view; delete doc; delete mainWindow; } KISTEST_MAIN(KisDerivedResourcesTest) diff --git a/libs/ui/tests/kis_node_model_test.cpp b/libs/ui/tests/kis_node_model_test.cpp index 5e13432edc..bc99acb19f 100644 --- a/libs/ui/tests/kis_node_model_test.cpp +++ b/libs/ui/tests/kis_node_model_test.cpp @@ -1,113 +1,115 @@ /* * 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_node_model_test.h" #include #include #include "KisDocument.h" #include "KisPart.h" #include "kis_node_model.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" #include #include "modeltest.h" void KisNodeModelTest::init() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); m_shapeController = new KisShapeController(m_doc, m_nameServer); m_nodeModel = new KisNodeModel(0); initBase(); + + m_doc->setCurrentImage(m_image); } void KisNodeModelTest::cleanup() { cleanupBase(); delete m_nodeModel; delete m_shapeController; delete m_nameServer; delete m_doc; } void KisNodeModelTest::testSetImage() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); } void KisNodeModelTest::testAddNode() { m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); constructImage(); } void KisNodeModelTest::testRemoveAllNodes() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); m_image->removeNode(m_layer4); m_image->removeNode(m_layer3); m_image->removeNode(m_layer2); m_image->removeNode(m_layer1); } void KisNodeModelTest::testRemoveIncludingRoot() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); m_image->removeNode(m_layer4); m_image->removeNode(m_layer3); m_image->removeNode(m_layer2); m_image->removeNode(m_layer1); m_image->removeNode(m_image->root()); } void KisNodeModelTest::testSubstituteRootNode() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); m_image->flatten(0); } KISTEST_MAIN(KisNodeModelTest) diff --git a/libs/ui/thememanager.cpp b/libs/ui/thememanager.cpp index 595b95fd98..52c7995f18 100644 --- a/libs/ui/thememanager.cpp +++ b/libs/ui/thememanager.cpp @@ -1,312 +1,312 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-08-02 * Description : theme manager * * Copyright (C) 2006-2011 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "thememanager.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include // Calligra #include #ifdef __APPLE__ #include #endif namespace Digikam { // --------------------------------------------------------------- class ThemeManager::ThemeManagerPriv { public: ThemeManagerPriv() : themeMenuActionGroup(0) , themeMenuAction(0) { } QString currentThemeName; QMap themeMap; // map QActionGroup* themeMenuActionGroup; KActionMenu* themeMenuAction; }; ThemeManager::ThemeManager(const QString &theme, QObject *parent) : QObject(parent) , d(new ThemeManagerPriv) { //qDebug() << "Creating theme manager with theme" << theme; d->currentThemeName = theme; populateThemeMap(); } ThemeManager::~ThemeManager() { delete d; } QString ThemeManager::currentThemeName() const { //qDebug() << "getting current themename"; QString themeName; if (d->themeMenuAction && d->themeMenuActionGroup) { QAction* action = d->themeMenuActionGroup->checkedAction(); if (action) { themeName = action->text().remove('&'); } //qDebug() << "\tthemename from action" << themeName; } else if (!d->currentThemeName.isEmpty()) { //qDebug() << "\tcurrent themename" << d->currentThemeName; themeName = d->currentThemeName; } else { //qDebug() << "\tfallback"; themeName = "Krita dark"; } //qDebug() << "\tresult" << themeName; return themeName; } void ThemeManager::setCurrentTheme(const QString& name) { //qDebug() << "setCurrentTheme();" << d->currentThemeName << "to" << name; d->currentThemeName = name; if (d->themeMenuAction && d->themeMenuActionGroup) { QList list = d->themeMenuActionGroup->actions(); Q_FOREACH (QAction* action, list) { if (action->text().remove('&') == name) { action->setChecked(true); } } } slotChangePalette(); } void ThemeManager::slotChangePalette() { //qDebug() << "slotChangePalette" << sender(); QString theme(currentThemeName()); QString filename = d->themeMap.value(theme); KSharedConfigPtr config = KSharedConfig::openConfig(filename); QPalette palette = qApp->palette(); QPalette::ColorGroup states[3] = { QPalette::Active, QPalette::Inactive, QPalette::Disabled }; // TT thinks tooltips shouldn't use active, so we use our active colors for all states KColorScheme schemeTooltip(QPalette::Active, KColorScheme::Tooltip, config); for ( int i = 0; i < 3 ; ++i ) { QPalette::ColorGroup state = states[i]; KColorScheme schemeView(state, KColorScheme::View, config); KColorScheme schemeWindow(state, KColorScheme::Window, config); KColorScheme schemeButton(state, KColorScheme::Button, config); KColorScheme schemeSelection(state, KColorScheme::Selection, config); palette.setBrush(state, QPalette::WindowText, schemeWindow.foreground()); palette.setBrush(state, QPalette::Window, schemeWindow.background()); palette.setBrush(state, QPalette::Base, schemeView.background()); palette.setBrush(state, QPalette::Text, schemeView.foreground()); palette.setBrush(state, QPalette::Button, schemeButton.background()); palette.setBrush(state, QPalette::ButtonText, schemeButton.foreground()); palette.setBrush(state, QPalette::Highlight, schemeSelection.background()); palette.setBrush(state, QPalette::HighlightedText, schemeSelection.foreground()); palette.setBrush(state, QPalette::ToolTipBase, schemeTooltip.background()); palette.setBrush(state, QPalette::ToolTipText, schemeTooltip.foreground()); palette.setColor(state, QPalette::Light, schemeWindow.shade(KColorScheme::LightShade)); palette.setColor(state, QPalette::Midlight, schemeWindow.shade(KColorScheme::MidlightShade)); palette.setColor(state, QPalette::Mid, schemeWindow.shade(KColorScheme::MidShade)); palette.setColor(state, QPalette::Dark, schemeWindow.shade(KColorScheme::DarkShade)); palette.setColor(state, QPalette::Shadow, schemeWindow.shade(KColorScheme::ShadowShade)); palette.setBrush(state, QPalette::AlternateBase, schemeView.background(KColorScheme::AlternateBackground)); palette.setBrush(state, QPalette::Link, schemeView.foreground(KColorScheme::LinkText)); palette.setBrush(state, QPalette::LinkVisited, schemeView.foreground(KColorScheme::VisitedText)); } //qDebug() << ">>>>>>>>>>>>>>>>>> going to set palette on app" << theme; // hint for the style to synchronize the color scheme with the window manager/compositor qApp->setProperty("KDE_COLOR_SCHEME_PATH", filename); qApp->setPalette(palette); #ifdef Q_OS_MACOS if (theme == "Krita bright" || theme.isEmpty()) { qApp->setStyle("Macintosh"); qApp->style()->polish(qApp); } else { qApp->setStyle("Fusion"); qApp->style()->polish(qApp); } #endif KisIconUtils::clearIconCache(); emit signalThemeChanged(); } void ThemeManager::setThemeMenuAction(KActionMenu* const action) { d->themeMenuAction = action; populateThemeMenu(); } void ThemeManager::registerThemeActions(KActionCollection *actionCollection) { if (!d->themeMenuAction) return; actionCollection->addAction("theme_menu", d->themeMenuAction); } void ThemeManager::populateThemeMenu() { if (!d->themeMenuAction) return; d->themeMenuAction->menu()->clear(); delete d->themeMenuActionGroup; d->themeMenuActionGroup = new QActionGroup(d->themeMenuAction); connect(d->themeMenuActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangePalette())); QAction * action; const QStringList schemeFiles = KoResourcePaths::findAllResources("data", "color-schemes/*.colors"); QMap actionMap; for (int i = 0; i < schemeFiles.size(); ++i) { const QString filename = schemeFiles.at(i); const QFileInfo info(filename); KSharedConfigPtr config = KSharedConfig::openConfig(filename); QIcon icon = createSchemePreviewIcon(config); KConfigGroup group(config, "General"); - const QString name = group.readEntry("Name", info.baseName()); + const QString name = group.readEntry("Name", info.completeBaseName()); action = new QAction(name, d->themeMenuActionGroup); action->setIcon(icon); action->setCheckable(true); actionMap.insert(name, action); } // sort the list QStringList actionMapKeys = actionMap.keys(); actionMapKeys.sort(); Q_FOREACH (const QString& name, actionMapKeys) { if ( name == currentThemeName()) { actionMap.value(name)->setChecked(true); } d->themeMenuAction->addAction(actionMap.value(name)); } } QPixmap ThemeManager::createSchemePreviewIcon(const KSharedConfigPtr& config) { // code taken from kdebase/workspace/kcontrol/colors/colorscm.cpp const uchar bits1[] = { 0xff, 0xff, 0xff, 0x2c, 0x16, 0x0b }; const uchar bits2[] = { 0x68, 0x34, 0x1a, 0xff, 0xff, 0xff }; const QSize bitsSize(24, 2); const QBitmap b1 = QBitmap::fromData(bitsSize, bits1); const QBitmap b2 = QBitmap::fromData(bitsSize, bits2); QPixmap pixmap(23, 16); pixmap.fill(Qt::black); // FIXME use some color other than black for borders? KConfigGroup group(config, "WM"); QPainter p(&pixmap); KColorScheme windowScheme(QPalette::Active, KColorScheme::Window, config); p.fillRect(1, 1, 7, 7, windowScheme.background()); p.fillRect(2, 2, 5, 2, QBrush(windowScheme.foreground().color(), b1)); KColorScheme buttonScheme(QPalette::Active, KColorScheme::Button, config); p.fillRect(8, 1, 7, 7, buttonScheme.background()); p.fillRect(9, 2, 5, 2, QBrush(buttonScheme.foreground().color(), b1)); p.fillRect(15, 1, 7, 7, group.readEntry("activeBackground", QColor(96, 148, 207))); p.fillRect(16, 2, 5, 2, QBrush(group.readEntry("activeForeground", QColor(255, 255, 255)), b1)); KColorScheme viewScheme(QPalette::Active, KColorScheme::View, config); p.fillRect(1, 8, 7, 7, viewScheme.background()); p.fillRect(2, 12, 5, 2, QBrush(viewScheme.foreground().color(), b2)); KColorScheme selectionScheme(QPalette::Active, KColorScheme::Selection, config); p.fillRect(8, 8, 7, 7, selectionScheme.background()); p.fillRect(9, 12, 5, 2, QBrush(selectionScheme.foreground().color(), b2)); p.fillRect(15, 8, 7, 7, group.readEntry("inactiveBackground", QColor(224, 223, 222))); p.fillRect(16, 12, 5, 2, QBrush(group.readEntry("inactiveForeground", QColor(20, 19, 18)), b2)); p.end(); return pixmap; } void ThemeManager::populateThemeMap() { const QStringList schemeFiles = KoResourcePaths::findAllResources("data", "color-schemes/*.colors"); for (int i = 0; i < schemeFiles.size(); ++i) { const QString filename = schemeFiles.at(i); const QFileInfo info(filename); KSharedConfigPtr config = KSharedConfig::openConfig(filename); KConfigGroup group(config, "General"); - const QString name = group.readEntry("Name", info.baseName()); + const QString name = group.readEntry("Name", info.completeBaseName()); d->themeMap.insert(name, filename); } } } // namespace Digikam diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 31baf0ed40..dcc797b033 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,417 +1,419 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPattern *currentPattern = 0; KoAbstractGradient *currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; + bool hasOverrideSelection = false; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-running actions * will have correct brush parameters. Theoretically this cloning * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ KisPaintOpPresetSP p = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (p) { m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->clone(); } #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset ? m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ - if (m_d->selectionOverride) { + if (m_d->hasOverrideSelection) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return m_d->currentPaintOpPreset->settings()->isAirbrushing(); } qreal KisResourcesSnapshot::airbrushingInterval() const { return m_d->currentPaintOpPreset->settings()->airbrushInterval(); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return m_d->currentPaintOpPreset->settings()->useSpacingUpdates(); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPattern* KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; + m_d->hasOverrideSelection = true; // needed if selection passed is null to ignore selection } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush; } diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp index 96ae0f9c0e..c496f3bc58 100644 --- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp @@ -1,403 +1,401 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_painter_based_stroke_strategy.h" #include #include #include #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_transaction.h" #include "kis_image.h" #include #include "kis_undo_stores.h" #include "KisFreehandStrokeInfo.h" #include "KisMaskedFreehandStrokePainter.h" #include "KisMaskingBrushRenderer.h" #include "KisRunnableStrokeJobData.h" #include "kis_paintop_preset.h" #include "kis_paintop_settings.h" KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const QString &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, QVector strokeInfos,bool useMergeID) : KisRunnableBasedStrokeStrategy(id, name), m_resources(resources), m_strokeInfos(strokeInfos), m_transaction(0), m_useMergeID(useMergeID), m_supportsMaskingBrush(false), m_supportsIndirectPainting(false) { init(); } KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const QString &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, KisFreehandStrokeInfo *strokeInfo,bool useMergeID) : KisRunnableBasedStrokeStrategy(id, name), m_resources(resources), m_strokeInfos(QVector() << strokeInfo), m_transaction(0), m_useMergeID(useMergeID), m_supportsMaskingBrush(false), m_supportsIndirectPainting(false) { init(); } KisPainterBasedStrokeStrategy::~KisPainterBasedStrokeStrategy() { } void KisPainterBasedStrokeStrategy::init() { enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); enableJob(KisSimpleStrokeStrategy::JOB_SUSPEND); enableJob(KisSimpleStrokeStrategy::JOB_RESUME); } KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const KisPainterBasedStrokeStrategy &rhs, int levelOfDetail) : KisRunnableBasedStrokeStrategy(rhs), m_resources(rhs.m_resources), m_transaction(rhs.m_transaction), m_useMergeID(rhs.m_useMergeID), m_supportsMaskingBrush(rhs.m_supportsMaskingBrush), m_supportsIndirectPainting(rhs.m_supportsIndirectPainting) { Q_FOREACH (KisFreehandStrokeInfo *info, rhs.m_strokeInfos) { m_strokeInfos.append(new KisFreehandStrokeInfo(info, levelOfDetail)); } KIS_ASSERT_RECOVER_NOOP( rhs.m_maskStrokeInfos.isEmpty() && !rhs.m_transaction && !rhs.m_targetDevice && !rhs.m_activeSelection && "After the stroke has been started, no copying must happen"); } KisPaintDeviceSP KisPainterBasedStrokeStrategy::targetDevice() const { return m_targetDevice; } KisSelectionSP KisPainterBasedStrokeStrategy::activeSelection() const { return m_activeSelection; } KisMaskedFreehandStrokePainter *KisPainterBasedStrokeStrategy::maskedPainter(int strokeInfoId) { return m_maskedPainters[strokeInfoId]; } int KisPainterBasedStrokeStrategy::numMaskedPainters() const { return m_maskedPainters.size(); } bool KisPainterBasedStrokeStrategy::needsMaskingUpdates() const { return m_maskingBrushRenderer; } QVector KisPainterBasedStrokeStrategy::doMaskingBrushUpdates(const QVector &rects) { QVector jobs; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_maskingBrushRenderer, jobs); Q_FOREACH (const QRect &rc, rects) { jobs.append(new KisRunnableStrokeJobData( [this, rc] () { this->m_maskingBrushRenderer->updateProjection(rc); }, KisStrokeJobData::CONCURRENT)); } return jobs; } void KisPainterBasedStrokeStrategy::setSupportsMaskingBrush(bool value) { m_supportsMaskingBrush = value; } bool KisPainterBasedStrokeStrategy::supportsMaskingBrush() const { return m_supportsMaskingBrush; } void KisPainterBasedStrokeStrategy::setSupportsIndirectPainting(bool value) { m_supportsIndirectPainting = value; } bool KisPainterBasedStrokeStrategy::supportsIndirectPainting() const { return m_supportsIndirectPainting; } void KisPainterBasedStrokeStrategy::initPainters(KisPaintDeviceSP targetDevice, KisPaintDeviceSP maskingDevice, KisSelectionSP selection, bool hasIndirectPainting, const QString &indirectPaintingCompositeOp) { Q_FOREACH (KisFreehandStrokeInfo *info, m_strokeInfos) { KisPainter *painter = info->painter; painter->begin(targetDevice, !hasIndirectPainting ? selection : 0); painter->setRunnableStrokeJobsInterface(runnableJobsInterface()); m_resources->setupPainter(painter); if(hasIndirectPainting) { painter->setCompositeOp(targetDevice->colorSpace()->compositeOp(indirectPaintingCompositeOp)); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); } } if (maskingDevice) { for (int i = 0; i < m_strokeInfos.size(); i++) { KisFreehandStrokeInfo *maskingInfo = new KisFreehandStrokeInfo(*m_strokeInfos[i]->dragDistance); KisPainter *painter = maskingInfo->painter; painter->begin(maskingDevice, 0); m_resources->setupMaskingBrushPainter(painter); KIS_SAFE_ASSERT_RECOVER_NOOP(hasIndirectPainting); m_maskStrokeInfos.append(maskingInfo); } } for (int i = 0; i < m_strokeInfos.size(); i++) { m_maskedPainters.append( new KisMaskedFreehandStrokePainter(m_strokeInfos[i], !m_maskStrokeInfos.isEmpty() ? m_maskStrokeInfos[i] : 0)); } } void KisPainterBasedStrokeStrategy::deletePainters() { Q_FOREACH (KisFreehandStrokeInfo *info, m_strokeInfos) { delete info; } m_strokeInfos.clear(); Q_FOREACH (KisFreehandStrokeInfo *info, m_maskStrokeInfos) { delete info; } m_maskStrokeInfos.clear(); Q_FOREACH (KisMaskedFreehandStrokePainter *info, m_maskedPainters) { delete info; } m_maskedPainters.clear(); } void KisPainterBasedStrokeStrategy::initStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisPaintDeviceSP paintDevice = node->paintDevice(); KisPaintDeviceSP targetDevice = paintDevice; bool hasIndirectPainting = supportsIndirectPainting() && m_resources->needsIndirectPainting(); const QString indirectCompositeOp = m_resources->indirectPaintingCompositeOp(); - - KisSelectionSP selection = m_resources->activeSelection(); + KisSelectionSP selection = m_resources->activeSelection(); if (hasIndirectPainting) { KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); if (indirect) { targetDevice = paintDevice->createCompositionSourceDevice(); targetDevice->setParentNode(node); indirect->setCurrentColor(m_resources->currentFgColor()); indirect->setTemporaryTarget(targetDevice); indirect->setTemporaryCompositeOp(m_resources->compositeOpId()); indirect->setTemporaryOpacity(m_resources->opacity()); indirect->setTemporarySelection(selection); QBitArray channelLockFlags = m_resources->channelLockFlags(); indirect->setTemporaryChannelFlags(channelLockFlags); } else { hasIndirectPainting = false; } } if (m_useMergeID) { m_transaction = new KisTransaction(name(), targetDevice, 0, timedID(this->id())); } else { m_transaction = new KisTransaction(name(), targetDevice); } - // WARNING: masked brush cannot work without indirect painting mode! KIS_SAFE_ASSERT_RECOVER_NOOP(!(supportsMaskingBrush() && m_resources->needsMaskingBrushRendering()) || hasIndirectPainting); if (hasIndirectPainting && supportsMaskingBrush() && m_resources->needsMaskingBrushRendering()) { const QString compositeOpId = m_resources->currentPaintOpPreset()->settings()->maskingBrushCompositeOp(); m_maskingBrushRenderer.reset(new KisMaskingBrushRenderer(targetDevice, compositeOpId)); initPainters(m_maskingBrushRenderer->strokeDevice(), m_maskingBrushRenderer->maskDevice(), selection, hasIndirectPainting, indirectCompositeOp); } else { initPainters(targetDevice, 0, selection, hasIndirectPainting, indirectCompositeOp); } m_targetDevice = targetDevice; m_activeSelection = selection; // sanity check: selection should be applied only once if (selection && !m_strokeInfos.isEmpty()) { KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); KIS_ASSERT_RECOVER_RETURN(hasIndirectPainting || m_strokeInfos.first()->painter->selection()); KIS_ASSERT_RECOVER_RETURN(!hasIndirectPainting || !indirect->temporarySelection() || !m_strokeInfos.first()->painter->selection()); } } void KisPainterBasedStrokeStrategy::finishStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); KisPostExecutionUndoAdapter *undoAdapter = m_resources->postExecutionUndoAdapter(); QScopedPointer dumbUndoAdapter; QScopedPointer dumbUndoStore; if (!undoAdapter) { dumbUndoStore.reset(new KisDumbUndoStore()); dumbUndoAdapter.reset(new KisPostExecutionUndoAdapter(dumbUndoStore.data(), 0)); undoAdapter = dumbUndoAdapter.data(); } if (indirect && indirect->hasTemporaryTarget()) { KUndo2MagicString transactionText = m_transaction->text(); m_transaction->end(); if(m_useMergeID){ indirect->mergeToLayer(node, undoAdapter, transactionText,timedID(this->id())); } else{ indirect->mergeToLayer(node, undoAdapter, transactionText); } } else { m_transaction->commit(undoAdapter); } delete m_transaction; deletePainters(); } void KisPainterBasedStrokeStrategy::cancelStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); bool revert = true; if (indirect) { KisPaintDeviceSP t = indirect->temporaryTarget(); if (t) { delete m_transaction; deletePainters(); QRegion region = t->region(); indirect->setTemporaryTarget(0); node->setDirty(region); revert = false; } } if (revert) { m_transaction->revert(); delete m_transaction; deletePainters(); } } void KisPainterBasedStrokeStrategy::suspendStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); if(indirect && indirect->hasTemporaryTarget()) { indirect->setTemporaryTarget(0); } } void KisPainterBasedStrokeStrategy::resumeStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); if(indirect) { // todo: don't ask about paint device // todo:change to an assert if (node->paintDevice() != m_targetDevice) { indirect->setTemporaryTarget(m_targetDevice); indirect->setTemporaryCompositeOp(m_resources->compositeOpId()); indirect->setTemporaryOpacity(m_resources->opacity()); indirect->setTemporarySelection(m_activeSelection); } } } KisNodeSP KisPainterBasedStrokeStrategy::targetNode() const { return m_resources->currentNode(); } diff --git a/libs/ui/widgets/KisDitherWidget.ui b/libs/ui/widgets/KisDitherWidget.ui index 54cd5d2ca8..e0f7d09920 100644 --- a/libs/ui/widgets/KisDitherWidget.ui +++ b/libs/ui/widgets/KisDitherWidget.ui @@ -1,217 +1,220 @@ KisDitherWidget 0 0 185 154 + + KisDitherWidget + 0 0 0 0 0 0 Threshold Mode thresholdModeComboBox Pattern Noise 0 0 QFrame::StyledPanel 1 Amount: 0 0 Pattern patternIconWidget ... Value Mode patternValueModeComboBox Auto Lightness Alpha Seed noiseSeedLineEdit Randomize Qt::ToolButtonTextOnly KisIconWidget QToolButton
kis_iconwidget.h
KisDoubleSliderSpinBox QDoubleSpinBox
kis_slider_spin_box.h
thresholdModeComboBox patternIconWidget patternValueModeComboBox noiseSeedLineEdit noiseSeedRandomizeButton thresholdModeComboBox currentIndexChanged(int) thresholdModeStackedWidget setCurrentIndex(int) 180 11 185 36 thresholdModeStackedWidget currentChanged(int) thresholdModeComboBox setCurrentIndex(int) 242 96 239 17
diff --git a/libs/ui/widgets/KisSelectionPropertySlider.cpp b/libs/ui/widgets/KisSelectionPropertySlider.cpp index 59f737470d..b7282c955d 100644 --- a/libs/ui/widgets/KisSelectionPropertySlider.cpp +++ b/libs/ui/widgets/KisSelectionPropertySlider.cpp @@ -1,89 +1,89 @@ /* * Copyright (C) 2018 Jouni Pentikäinen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 "KisSelectionPropertySlider.h" struct KisSelectionPropertySliderBase::Private { KisSignalCompressor *signalCompressor; QString normalPrefix; QString mixedPrefix; explicit Private(KisSelectionPropertySliderBase *q) : signalCompressor(new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE, q)) {} }; KisSelectionPropertySliderBase::KisSelectionPropertySliderBase(QWidget *parent) : KisDoubleSliderSpinBox(parent) , m_d(new Private(this)) { connect(m_d->signalCompressor, SIGNAL(timeout()), SLOT(slotCompressedUpdate())); } KisSelectionPropertySliderBase::~KisSelectionPropertySliderBase() {} void KisSelectionPropertySliderBase::setPrefixes(const QString &normalPrefix, const QString &mixedPrefix) { m_d->normalPrefix = normalPrefix; m_d->mixedPrefix = mixedPrefix; setPrefix(normalPrefix); } void KisSelectionPropertySliderBase::setInternalValue(int _value, bool blockUpdateSignal) { static const qreal eps = 1e-3; if (!hasSelection()) return; setPrivateValue(_value); const qreal newValue = value(); const qreal commonValue = getCommonValue(); if (qAbs(commonValue - newValue) < eps) { return; } if(!blockUpdateSignal) { m_d->signalCompressor->start(); } } void KisSelectionPropertySliderBase::slotCompressedUpdate() { emit(valueChanged(value())); } void KisSelectionPropertySliderBase::setSelectionValue(qreal commonValue, bool mixed) { if (mixed) { - setValue(0.0); + setInternalValue(0.0, true); // BUG:409131 setPrefix(m_d->mixedPrefix); } else { setValue(commonValue); setPrefix(m_d->normalPrefix); } } KisShapePropertySlider::KisShapePropertySlider(QWidget *parent) : KisSelectionPropertySlider::KisSelectionPropertySlider(parent) {} diff --git a/libs/ui/widgets/KoDualColorButton.cpp b/libs/ui/widgets/KoDualColorButton.cpp index 412383274e..acc01f54f6 100644 --- a/libs/ui/widgets/KoDualColorButton.cpp +++ b/libs/ui/widgets/KoDualColorButton.cpp @@ -1,406 +1,402 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Daniel M. Duley This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoDualColorButton.h" #include "KoColor.h" #include "KoColorDisplayRendererInterface.h" #include #include "dcolorarrow.xbm" #include "dcolorreset.xpm" #include #include "KisDlgInternalColorSelector.h" #include "kis_signals_blocker.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoDualColorButton::Private { - public: +public: Private(const KoColor &fgColor, const KoColor &bgColor, QWidget *_dialogParent, const KoColorDisplayRendererInterface *_displayRenderer) : dialogParent(_dialogParent) , dragFlag( false ) , miniCtlFlag( false ) , foregroundColor(fgColor) , backgroundColor(bgColor) , displayRenderer(_displayRenderer) { updateArrows(); resetPixmap = QPixmap( (const char **)dcolorreset_xpm ); popDialog = true; } void updateArrows() { arrowBitmap = QPixmap(12,12); arrowBitmap.fill(Qt::transparent); QPainter p(&arrowBitmap); p.setPen(dialogParent->palette().foreground().color()); // arrow pointing left p.drawLine(0, 3, 7, 3); p.drawLine(1, 2, 1, 4); p.drawLine(2, 1, 2, 5); p.drawLine(3, 0, 3, 6); // arrow pointing down p.drawLine(8, 4, 8, 11); p.drawLine(5, 8, 11, 8); p.drawLine(6, 9, 10, 9); p.drawLine(7, 10, 9, 10); } QWidget* dialogParent; QPixmap arrowBitmap; QPixmap resetPixmap; bool dragFlag, miniCtlFlag; KoColor foregroundColor; KoColor backgroundColor; KisDlgInternalColorSelector *colorSelectorDialog; QPoint dragPosition; Selection tmpSelection; bool popDialog; const KoColorDisplayRendererInterface *displayRenderer; void init(KoDualColorButton *q); }; void KoDualColorButton::Private::init(KoDualColorButton *q) { if ( q->sizeHint().isValid() ) q->setMinimumSize( q->sizeHint() ); q->setAcceptDrops( true ); QString caption = i18n("Select a Color"); KisDlgInternalColorSelector::Config config = KisDlgInternalColorSelector::Config(); config.modal = false; colorSelectorDialog = new KisDlgInternalColorSelector(q, foregroundColor, config, caption, displayRenderer); connect(colorSelectorDialog, SIGNAL(signalForegroundColorChosen(KoColor)), q, SLOT(slotSetForeGroundColorFromDialog(KoColor))); connect(q, SIGNAL(foregroundColorChanged(KoColor)), colorSelectorDialog, SLOT(slotColorUpdated(KoColor))); } KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, QWidget *parent, QWidget* dialogParent ) : QWidget( parent ), d( new Private(foregroundColor, backgroundColor, dialogParent, KoDumbColorDisplayRenderer::instance()) ) { d->init(this); } KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent, QWidget* dialogParent) : QWidget( parent ), d( new Private(foregroundColor, backgroundColor, dialogParent, displayRenderer) ) { d->init(this); } KoDualColorButton::~KoDualColorButton() { - delete d; + delete d; } KoColor KoDualColorButton::foregroundColor() const { - return d->foregroundColor; + return d->foregroundColor; } KoColor KoDualColorButton::backgroundColor() const { - return d->backgroundColor; + return d->backgroundColor; } bool KoDualColorButton::popDialog() const { - return d->popDialog; + return d->popDialog; } QSize KoDualColorButton::sizeHint() const { - return QSize( 34, 34 ); + return QSize( 34, 34 ); } -void KoDualColorButton::setForegroundColor( const KoColor &color ) +void KoDualColorButton::setForegroundColor(const KoColor &color) { - d->foregroundColor = color; - { - /** + d->foregroundColor = color; + { + /** * The internal color selector might emit the color of a different profile, so * we should break this cycling dependency somehow. */ - KisSignalsBlocker b(d->colorSelectorDialog); - d->colorSelectorDialog->slotColorUpdated(color); - } - repaint(); + KisSignalsBlocker b(d->colorSelectorDialog); + d->colorSelectorDialog->slotColorUpdated(color); + } + repaint(); } void KoDualColorButton::setBackgroundColor( const KoColor &color ) { - d->backgroundColor = color; - repaint(); + d->backgroundColor = color; + repaint(); } void KoDualColorButton::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { d->displayRenderer = displayRenderer; d->colorSelectorDialog->setDisplayRenderer(displayRenderer); connect(d->displayRenderer, SIGNAL(destroyed()), this, SLOT(setDisplayRenderer()), Qt::UniqueConnection); } else { d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KoDualColorButton::setColorSpace(const KoColorSpace *cs) { d->colorSelectorDialog->lockUsedColorSpace(cs); } QColor KoDualColorButton::getColorFromDisplayRenderer(KoColor c) { QColor col; if (d->displayRenderer) { c.convertTo(d->displayRenderer->getPaintingColorSpace()); col = d->displayRenderer->toQColor(c); } else { col = c.toQColor(); } return col; } void KoDualColorButton::setPopDialog( bool popDialog ) { - d->popDialog = popDialog; + d->popDialog = popDialog; } void KoDualColorButton::metrics( QRect &foregroundRect, QRect &backgroundRect ) { - foregroundRect = QRect( 0, 0, width() - 14, height() - 14 ); - backgroundRect = QRect( 14, 14, width() - 14, height() - 14 ); + foregroundRect = QRect( 0, 0, width() - 14, height() - 14 ); + backgroundRect = QRect( 14, 14, width() - 14, height() - 14 ); } void KoDualColorButton::paintEvent(QPaintEvent *) { - QRect foregroundRect; - QRect backgroundRect; + QRect foregroundRect; + QRect backgroundRect; - QPainter painter( this ); + QPainter painter( this ); - metrics( foregroundRect, backgroundRect ); + metrics( foregroundRect, backgroundRect ); - QBrush defBrush = palette().brush( QPalette::Button ); - QBrush foregroundBrush( getColorFromDisplayRenderer(d->foregroundColor), Qt::SolidPattern ); - QBrush backgroundBrush( getColorFromDisplayRenderer(d->backgroundColor), Qt::SolidPattern ); + QBrush defBrush = palette().brush( QPalette::Button ); + QBrush foregroundBrush( getColorFromDisplayRenderer(d->foregroundColor), Qt::SolidPattern ); + QBrush backgroundBrush( getColorFromDisplayRenderer(d->backgroundColor), Qt::SolidPattern ); - qDrawShadeRect( &painter, backgroundRect, palette(), false, 1, 0, - isEnabled() ? &backgroundBrush : &defBrush ); + qDrawShadeRect( &painter, backgroundRect, palette(), false, 1, 0, + isEnabled() ? &backgroundBrush : &defBrush ); - qDrawShadeRect( &painter, foregroundRect, palette(), false, 1, 0, - isEnabled() ? &foregroundBrush : &defBrush ); + qDrawShadeRect( &painter, foregroundRect, palette(), false, 1, 0, + isEnabled() ? &foregroundBrush : &defBrush ); - painter.setPen( palette().color( QPalette::Shadow ) ); + painter.setPen( palette().color( QPalette::Shadow ) ); - painter.drawPixmap( foregroundRect.right() + 2, 1, d->arrowBitmap ); - painter.drawPixmap( 1, foregroundRect.bottom() + 2, d->resetPixmap ); + painter.drawPixmap( foregroundRect.right() + 2, 1, d->arrowBitmap ); + painter.drawPixmap( 1, foregroundRect.bottom() + 2, d->resetPixmap ); } void KoDualColorButton::dragEnterEvent( QDragEnterEvent *event ) { - event->setAccepted( isEnabled() && KColorMimeData::canDecode( event->mimeData() ) ); + event->setAccepted( isEnabled() && KColorMimeData::canDecode( event->mimeData() ) ); } void KoDualColorButton::dropEvent( QDropEvent *event ) { Q_UNUSED(event); -/* QColor color = KColorMimeData::fromMimeData( event->mimeData() ); + /* QColor color = KColorMimeData::fromMimeData( event->mimeData() ); if ( color.isValid() ) { if ( d->selection == Foreground ) { d->foregroundColor = color; emit foregroundColorChanged( color ); } else { d->backgroundColor = color; emit backgroundColorChanged( color ); } repaint(); } */ } void KoDualColorButton::slotSetForeGroundColorFromDialog(const KoColor color) { d->foregroundColor = color; repaint(); emit foregroundColorChanged(d->foregroundColor); } void KoDualColorButton::mousePressEvent( QMouseEvent *event ) { - QRect foregroundRect; - QRect backgroundRect; + QRect foregroundRect; + QRect backgroundRect; - metrics( foregroundRect, backgroundRect ); + metrics( foregroundRect, backgroundRect ); - d->dragPosition = event->pos(); + d->dragPosition = event->pos(); - d->dragFlag = false; + d->dragFlag = false; - if ( foregroundRect.contains( d->dragPosition ) ) { - d->tmpSelection = Foreground; - d->miniCtlFlag = false; - } - else if( backgroundRect.contains( d->dragPosition ) ) { - d->tmpSelection = Background; - d->miniCtlFlag = false; - } - else if ( event->pos().x() > foregroundRect.width() ) { - // We handle the swap and reset controls as soon as the mouse is - // is pressed and ignore further events on this click (mosfet). + if ( foregroundRect.contains( d->dragPosition ) ) { + d->tmpSelection = Foreground; + d->miniCtlFlag = false; + } + else if( backgroundRect.contains( d->dragPosition ) ) { + d->tmpSelection = Background; + d->miniCtlFlag = false; + } + else if ( event->pos().x() > foregroundRect.width() ) { + // We handle the swap and reset controls as soon as the mouse is + // is pressed and ignore further events on this click (mosfet). - KoColor tmp = d->foregroundColor; - d->foregroundColor = d->backgroundColor; - d->backgroundColor = tmp; + KoColor tmp = d->foregroundColor; + d->foregroundColor = d->backgroundColor; + d->backgroundColor = tmp; - emit backgroundColorChanged( d->backgroundColor ); - emit foregroundColorChanged( d->foregroundColor ); + emit backgroundColorChanged( d->backgroundColor ); + emit foregroundColorChanged( d->foregroundColor ); - d->miniCtlFlag = true; - } - else if ( event->pos().x() < backgroundRect.x() ) { - d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::black); - d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::white); + d->miniCtlFlag = true; + } + else if ( event->pos().x() < backgroundRect.x() ) { + d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::black); + d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::white); - emit backgroundColorChanged( d->backgroundColor ); - emit foregroundColorChanged( d->foregroundColor ); + emit backgroundColorChanged( d->backgroundColor ); + emit foregroundColorChanged( d->foregroundColor ); - d->miniCtlFlag = true; - } - repaint(); + d->miniCtlFlag = true; + } + repaint(); } void KoDualColorButton::mouseMoveEvent( QMouseEvent *event ) { - if ( !d->miniCtlFlag ) { - int delay = QApplication::startDragDistance(); - - if ( event->x() >= d->dragPosition.x() + delay || event->x() <= d->dragPosition.x() - delay || - event->y() >= d->dragPosition.y() + delay || event->y() <= d->dragPosition.y() - delay ) { - KColorMimeData::createDrag( d->tmpSelection == Foreground ? - getColorFromDisplayRenderer(d->foregroundColor) : - getColorFromDisplayRenderer(d->backgroundColor), - this )->start(); - d->dragFlag = true; + if ( !d->miniCtlFlag ) { + int delay = QApplication::startDragDistance(); + + if ( event->x() >= d->dragPosition.x() + delay || event->x() <= d->dragPosition.x() - delay || + event->y() >= d->dragPosition.y() + delay || event->y() <= d->dragPosition.y() - delay ) { + KColorMimeData::createDrag( d->tmpSelection == Foreground ? + getColorFromDisplayRenderer(d->foregroundColor) : + getColorFromDisplayRenderer(d->backgroundColor), + this )->start(); + d->dragFlag = true; + } } - } } void KoDualColorButton::mouseReleaseEvent( QMouseEvent *event ) { d->dragFlag = false; if ( d->miniCtlFlag ) return; d->miniCtlFlag = false; QRect foregroundRect; QRect backgroundRect; metrics( foregroundRect, backgroundRect ); if (foregroundRect.contains( event->pos())) { if (d->tmpSelection == Foreground) { if (d->popDialog) { #ifndef Q_OS_MACOS d->colorSelectorDialog->setPreviousColor(d->foregroundColor); - //this should toggle, but I don't know how to implement that... + // this should toggle, but I don't know how to implement that... d->colorSelectorDialog->show(); #else - QColor c = d->displayRenderer->toQColor(d->foregroundColor); + QColor c = d->foregroundColor.toQColor(); c = QColorDialog::getColor(c, this); if (c.isValid()) { d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(c); emit foregroundColorChanged(d->foregroundColor); } #endif } - else { - emit pleasePopDialog(d->foregroundColor); - } } else { d->foregroundColor = d->backgroundColor; emit foregroundColorChanged( d->foregroundColor ); } - } else if ( backgroundRect.contains( event->pos() )) { + } + else if ( backgroundRect.contains( event->pos() )) { if(d->tmpSelection == Background ) { if( d->popDialog) { #ifndef Q_OS_MACOS KoColor c = d->backgroundColor; - c = KisDlgInternalColorSelector::getModalColorDialog(c, this); + c = KisDlgInternalColorSelector::getModalColorDialog(c, this, d->colorSelectorDialog->windowTitle()); d->backgroundColor = c; emit backgroundColorChanged(d->backgroundColor); #else - QColor c = d->displayRenderer->toQColor(d->backgroundColor); + QColor c = d->backgroundColor.toQColor(); c = QColorDialog::getColor(c, this); if (c.isValid()) { d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(c); emit backgroundColorChanged(d->backgroundColor); } #endif } - else - emit pleasePopDialog( d->backgroundColor); } else { d->backgroundColor = d->foregroundColor; emit backgroundColorChanged( d->backgroundColor ); } } repaint(); } void KoDualColorButton::changeEvent(QEvent *event) { QWidget::changeEvent(event); switch (event->type()) { case QEvent::StyleChange: case QEvent::PaletteChange: d->updateArrows(); default: break; } } diff --git a/libs/ui/widgets/KoDualColorButton.h b/libs/ui/widgets/KoDualColorButton.h index 6c6e22067d..f5c4deca4f 100644 --- a/libs/ui/widgets/KoDualColorButton.h +++ b/libs/ui/widgets/KoDualColorButton.h @@ -1,186 +1,179 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Daniel M. Duley 2006 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KODUALCOLORBUTTON_H #define KODUALCOLORBUTTON_H #include #include #include class KoColor; class KoColorSpace; /** * @short A widget for selecting two related colors. * * KoDualColorButton allows the user to select two cascaded colors (usually a * foreground and background color). Other features include drag and drop * from other KDE color widgets, a reset to black and white control, and a * swap colors control. * * When the user clicks on the foreground or background rectangle the * rectangle is first sunken and the selectionChanged() signal is emitted. * Further clicks will present a color dialog and emit either the foregroundColorChanged() * or backgroundColorChanged() if a new color is selected. * * Note: With drag and drop when dropping a color the current selected color * will be set, while when dragging a color it will use whatever color * rectangle the mouse was pressed inside. * * @author Daniel M. Duley */ class KRITAUI_EXPORT KoDualColorButton : public QWidget { Q_OBJECT Q_ENUMS( Selection ) Q_PROPERTY( KoColor foregroundColor READ foregroundColor WRITE setForegroundColor ) Q_PROPERTY( KoColor backgroundColor READ backgroundColor WRITE setBackgroundColor ) Q_PROPERTY( bool popDialog READ popDialog WRITE setPopDialog ) public: enum Selection { Foreground, Background }; /** * Constructs a new KoDualColorButton with the supplied foreground and * background colors. * * @param parent The parent widget of the KoDualColorButton. * @param dialogParent The parent widget of the color selection dialog. */ KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, QWidget *parent = 0, QWidget* dialogParent = 0 ); KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent = 0, QWidget* dialogParent = 0 ); /** * Destroys the KoDualColorButton. */ ~KoDualColorButton() override; /** * Returns the current foreground color. */ KoColor foregroundColor() const; /** * Returns the current background color. */ KoColor backgroundColor() const; /** * Returns if a dialog with a color chooser will be popped up when clicking * If false then you could/should connect to the pleasePopDialog signal * and pop your own dialog. Just set the current color afterwards. */ bool popDialog() const; /** * Returns the minimum size needed to display the widget and all its * controls. */ QSize sizeHint() const override; public Q_SLOTS: /** * Sets the foreground color. */ void setForegroundColor( const KoColor &color ); /** * Sets the background color. */ void setBackgroundColor( const KoColor &color ); void slotSetForeGroundColorFromDialog (const KoColor color); void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance()); /** * @brief setColorSpace * set ColorSpace so we can lock the selector. Right now this'll be changed per view-change. * @param cs */ void setColorSpace(const KoColorSpace *cs); /** * @brief getColorFromDisplayRenderer * convenience function to get the right qcolor from the display renderer, including checking * whether the display renderer actually exists. * @param c the kocolor to convert. * @return the qcolor to use for display. */ QColor getColorFromDisplayRenderer(KoColor c); /** * Sets if a dialog with a color chooser should be popped up when clicking * If you set this to false then you could connect to the pleasePopDialog signal * and pop your own dialog. Just set the current color afterwards. */ void setPopDialog( bool popDialog ); Q_SIGNALS: /** * Emitted when the foreground color is changed. */ void foregroundColorChanged( const KoColor &color ); /** * Emitted when the background color is changed. */ void backgroundColorChanged( const KoColor &color ); - /** - * Emitted when the user clicks one of the two color patches. - * You should/could pop you own color chooser dialog in response. - * Also see the popDialog attribute. - */ - void pleasePopDialog( const KoColor &color ); - protected: /** * Sets the supplied rectangles to the proper size and position for the * current widget size. You can reimplement this to change the layout * of the widget. Restrictions are that the swap control will always * be at the top right, the reset control will always be at the bottom * left, and you must leave at least a 14x14 space in those corners. */ virtual void metrics( QRect &foregroundRect, QRect &backgroundRect ); void paintEvent( QPaintEvent *event ) override; void mousePressEvent( QMouseEvent *event ) override; void mouseMoveEvent( QMouseEvent *event ) override; void mouseReleaseEvent( QMouseEvent *event ) override; void dragEnterEvent( QDragEnterEvent *event ) override; void dropEvent( QDropEvent *event ) override; void changeEvent(QEvent *event) override; private: class Private; Private *const d; }; #endif diff --git a/libs/ui/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc index c1ae3bbe8c..932549c5da 100644 --- a/libs/ui/widgets/kis_color_space_selector.cc +++ b/libs/ui/widgets/kis_color_space_selector.cc @@ -1,237 +1,272 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_space_selector.h" #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselector.h" struct KisColorSpaceSelector::Private { Ui_WdgColorSpaceSelector* colorSpaceSelector; QString knsrcFile; bool profileValid; QString defaultsuffix; + bool profileSetManually; + KoID previousModel; }; KisColorSpaceSelector::KisColorSpaceSelector(QWidget* parent) : QWidget(parent), m_advancedSelector(0), d(new Private) { setObjectName("KisColorSpaceSelector"); d->colorSpaceSelector = new Ui_WdgColorSpaceSelector; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), - this, SLOT(fillCmbDepths(KoID))); + this, SLOT(slotModelsComboBoxActivated(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), - this, SLOT(fillCmbProfiles())); - connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), - this, SLOT(fillCmbProfiles())); + this, SLOT(slotDepthsComboBoxActivated())); connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(QString)), - this, SLOT(colorSpaceChanged())); + this, SLOT(slotProfilesComboBoxActivated())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); d->defaultsuffix = " "+i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"); + d->profileSetManually = false; + d->previousModel = d->colorSpaceSelector->cmbColorModels->currentItem(); connect(d->colorSpaceSelector->bnAdvanced, SIGNAL(clicked()), this, SLOT(slotOpenAdvancedSelector())); fillCmbProfiles(); } KisColorSpaceSelector::~KisColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisColorSpaceSelector::fillCmbProfiles() { + const QString currentProfileName = d->colorSpaceSelector->cmbProfile->currentUnsqueezedText(); + const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName + d->defaultsuffix); } else { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName); } } - d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix); + if (d->profileSetManually && profileNames.contains(currentProfileName)) { + d->colorSpaceSelector->cmbProfile->setCurrent(currentProfileName); + } else { + d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix); + } colorSpaceChanged(); } void KisColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); d->colorSpaceSelector->cmbColorDepth->setIDList(depths, false); if (depths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } const KoColorSpace* KisColorSpaceSelector::currentColorSpace() { - QString profilenamestring = d->colorSpaceSelector->cmbProfile->itemHighlighted(); + QString profilenamestring = d->colorSpaceSelector->cmbProfile->currentUnsqueezedText(); if (profilenamestring.contains(d->defaultsuffix)) { profilenamestring.remove(d->defaultsuffix); return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } else { return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } } void KisColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); + d->previousModel = id; fillCmbDepths(id); } void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); - fillCmbProfiles(); + if (!d->profileSetManually) { + fillCmbProfiles(); + } } void KisColorSpaceSelector::setCurrentProfile(const QString& name) { d->colorSpaceSelector->cmbProfile->setCurrent(name); } void KisColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisColorSpaceSelector::showColorBrowserButton(bool showButton) { d->colorSpaceSelector->bnAdvanced->setVisible(showButton); } void KisColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->cmbProfile->count() != 0; d->profileValid = valid; emit(selectionChanged(valid)); if(valid) { emit colorSpaceChanged(currentColorSpace()); QString text = currentColorSpace()->profile()->name(); } } void KisColorSpaceSelector::installProfile() { QStringList mime; 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) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillCmbProfiles(); } void KisColorSpaceSelector::slotOpenAdvancedSelector() { if (!m_advancedSelector) { m_advancedSelector = new KisAdvancedColorSpaceSelector(this, i18n("Select a Colorspace")); m_advancedSelector->setModal(true); if (currentColorSpace()) { m_advancedSelector->setCurrentColorSpace(currentColorSpace()); } connect(m_advancedSelector, SIGNAL(selectionChanged(bool)), this, SLOT(slotProfileValid(bool)) ); } QDialog::DialogCode result = (QDialog::DialogCode)m_advancedSelector->exec(); if (result) { if (d->profileValid==true) { setCurrentColorSpace(m_advancedSelector->currentColorSpace()); + d->profileSetManually = true; } } } void KisColorSpaceSelector::slotProfileValid(bool valid) { d->profileValid = valid; } + +void KisColorSpaceSelector::slotModelsComboBoxActivated(const KoID& id) +{ + if (d->previousModel != id) { + d->previousModel = id; + d->profileSetManually = false; + fillCmbDepths(id); + fillCmbProfiles(); + } +} + +void KisColorSpaceSelector::slotDepthsComboBoxActivated() +{ + fillCmbProfiles(); +} + + +void KisColorSpaceSelector::slotProfilesComboBoxActivated() +{ + d->profileSetManually = true; + colorSpaceChanged(); +} + diff --git a/libs/ui/widgets/kis_color_space_selector.h b/libs/ui/widgets/kis_color_space_selector.h index 5173aa55b6..99a08a28db 100644 --- a/libs/ui/widgets/kis_color_space_selector.h +++ b/libs/ui/widgets/kis_color_space_selector.h @@ -1,65 +1,69 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_COLOR_SPACE_SELECTOR_H_ #define _KIS_COLOR_SPACE_SELECTOR_H_ #include #include class KoID; class KoColorSpace; class KisAdvancedColorSpaceSelector; class KRITAUI_EXPORT KisColorSpaceSelector : public QWidget { Q_OBJECT public: KisColorSpaceSelector(QWidget* parent); ~KisColorSpaceSelector() override; const KoColorSpace* currentColorSpace(); void setCurrentColorModel(const KoID& id); void setCurrentColorDepth(const KoID& id); void setCurrentProfile(const QString& name); void setCurrentColorSpace(const KoColorSpace* colorSpace); void showColorBrowserButton(bool showButton); Q_SIGNALS: /** * This signal is emitted when a new color space is selected. * @param valid indicates if the color space can be used */ void selectionChanged(bool valid); /// This signal is emitted, when a new color space is selected, that can be used (eg is valid) void colorSpaceChanged(const KoColorSpace*); private Q_SLOTS: void fillCmbDepths(const KoID& idd); void fillCmbProfiles(); void colorSpaceChanged(); void installProfile(); void slotOpenAdvancedSelector(); void slotProfileValid(bool valid); + + void slotModelsComboBoxActivated(const KoID& id); + void slotDepthsComboBoxActivated(); + void slotProfilesComboBoxActivated(); private: struct Private; KisAdvancedColorSpaceSelector *m_advancedSelector; Private * const d; }; #endif diff --git a/libs/ui/widgets/kis_image_from_clipboard_widget.cpp b/libs/ui/widgets/kis_image_from_clipboard_widget.cpp index f609e1b82b..acca43b3da 100644 --- a/libs/ui/widgets/kis_image_from_clipboard_widget.cpp +++ b/libs/ui/widgets/kis_image_from_clipboard_widget.cpp @@ -1,139 +1,140 @@ /* 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_image_from_clipboard_widget.h" #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 "kis_import_catcher.h" #include "kis_clipboard.h" #include "KisDocument.h" #include "widgets/kis_cmb_idlist.h" #include KisImageFromClipboard::KisImageFromClipboard(QWidget* parent, qint32 defWidth, qint32 defHeight, double resolution, const QString& defColorModel, const QString& defColorDepth, const QString& defColorProfile, const QString& imageName) : KisCustomImageWidget(parent, defWidth, defHeight, resolution, defColorModel, defColorDepth, defColorProfile, imageName) { setObjectName("KisImageFromClipboard"); // create clipboard preview and show it createClipboardPreview(); grpClipboard->show(); imageGroupSpacer->changeSize(20, 40, QSizePolicy::Expanding, QSizePolicy::Expanding); 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())); disconnect(newDialogConfirmationButtonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), 0, 0); //disable normal signal connect(newDialogConfirmationButtonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(createImage())); setNumberOfLayers(1); } KisImageFromClipboard::~KisImageFromClipboard() { } void KisImageFromClipboard::createImage() { KisDocument *doc = createNewImage(); - + if (!doc) return; // createNewImage can return 0; + KisImageSP image = doc->image(); if (image && image->root() && image->root()->firstChild()) { KisLayer * layer = qobject_cast(image->root()->firstChild().data()); KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true); if (clip) { KisImportCatcher::adaptClipToImageColorSpace(clip, image); QRect r = clip->exactBounds(); KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), r); layer->setDirty(); } } doc->setModified(true); emit m_openPane->documentSelected(doc); } void KisImageFromClipboard::clipboardDataChanged() { createClipboardPreview(); } void KisImageFromClipboard::createClipboardPreview() { QClipboard *cb = QApplication::clipboard(); const QMimeData *cbData = cb->mimeData(); if (cbData->hasImage()) { QImage qimage = cb->image(); QByteArray mimeType("application/x-krita-selection"); if ((cbData && cbData->hasFormat(mimeType)) || !qimage.isNull()) { lblPreview->setPixmap(QPixmap::fromImage(qimage.scaled(75, 75, Qt::KeepAspectRatio))); lblPreview->show(); newDialogConfirmationButtonBox->button(QDialogButtonBox::Ok)->setEnabled(true); doubleWidth->setValue(qimage.width()); doubleHeight->setValue(qimage.height()); } } else { newDialogConfirmationButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); lblPreview->hide(); } } diff --git a/libs/widgets/KoResourceServer.h b/libs/widgets/KoResourceServer.h index a2081680ca..36d82dbb3f 100644 --- a/libs/widgets/KoResourceServer.h +++ b/libs/widgets/KoResourceServer.h @@ -1,760 +1,760 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (c) 2007 Jan Hambrecht Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2013 Sascha Suelzer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCESERVER_H #define KORESOURCESERVER_H #include #include #include #include #include #include #include #include #include "resources/KoResource.h" #include "KoResourceServerPolicies.h" #include "KoResourceServerObserver.h" #include "KoResourceTagStore.h" #include "KoResourcePaths.h" #include #include #include "kritawidgets_export.h" #include "WidgetsDebug.h" class KoResource; /** * KoResourceServerBase is the base class of all resource servers */ class KRITAWIDGETS_EXPORT KoResourceServerBase { public: /** * Constructs a KoResourceServerBase * @param type type, has to be the same as used by KoResourcePaths * @param extensions the file extensions separate by ':', e.g. *.svg:*.ggr" */ KoResourceServerBase(const QString& type, const QString& extensions) : m_type(type) , m_extensions(extensions) { } virtual ~KoResourceServerBase() {} virtual int resourceCount() const = 0; virtual void loadResources(QStringList filenames) = 0; virtual QStringList blackListedFiles() = 0; virtual QStringList queryResources(const QString &query) const = 0; QString type() const { return m_type; } /** * File extensions for resources of the server * @returns the file extensions separated by ':', e.g. "*.svg:*.ggr" */ QString extensions() const { return m_extensions; } QStringList fileNames() { QStringList extensionList = m_extensions.split(':'); QStringList fileNames; foreach (const QString &extension, extensionList) { fileNames += KoResourcePaths::findAllResources(type().toLatin1(), extension, KoResourcePaths::Recursive); } return fileNames; } protected: QStringList m_blackListFileNames; friend class KoResourceTagStore; virtual KoResource *byMd5(const QByteArray &md5) const = 0; virtual KoResource *byFileName(const QString &fileName) const = 0; private: QString m_type; QString m_extensions; protected: QMutex m_loadLock; }; /** * KoResourceServer manages the resources of one type. It stores, * loads and saves the resources. To keep track of changes the server * can be observed with a KoResourceServerObserver * * The \p Policy template parameter defines the way how the lifetime * of a resource is handled. There are to predefined policies: * * o PointerStoragePolicy --- usual pointers with ownership over * the resource. * o SharedPointerStoragePolicy --- shared pointers. The server does no * extra handling for the lifetime of * the resource. * * Use the former for usual resources and the latter for shared pointer based * ones. */ template > class KoResourceServer : public KoResourceServerBase { public: typedef typename Policy::PointerType PointerType; typedef KoResourceServerObserver ObserverType; KoResourceServer(const QString& type, const QString& extensions) : KoResourceServerBase(type, extensions) { m_blackListFile = KoResourcePaths::locateLocal("data", type + ".blacklist"); m_blackListFileNames = readBlackListFile(); m_tagStore = new KoResourceTagStore(this); } ~KoResourceServer() override { if (m_tagStore) { delete m_tagStore; } Q_FOREACH (ObserverType* observer, m_observers) { observer->unsetResourceServer(); } Q_FOREACH (PointerType res, m_resources) { Policy::deleteResource(res); } m_resources.clear(); } int resourceCount() const override { return m_resources.size(); } /** * Loads a set of resources and adds them to the resource server. * If a filename appears twice the resource will only be added once. Resources that can't * be loaded or and invalid aren't added to the server. * @param filenames list of filenames to be loaded */ void loadResources(QStringList filenames) override { QStringList uniqueFiles; while (!filenames.empty()) { QString front = filenames.first(); filenames.pop_front(); // In the save location, people can use sub-folders... And then they probably want // to load both versions! See https://bugs.kde.org/show_bug.cgi?id=321361. QString fname; if (front.contains(saveLocation())) { fname = front.split(saveLocation())[1]; } else { fname = QFileInfo(front).fileName(); } // XXX: Don't load resources with the same filename. Actually, we should look inside // the resource to find out whether they are really the same, but for now this // will prevent the same brush etc. showing up twice. if (!uniqueFiles.contains(fname)) { m_loadLock.lock(); uniqueFiles.append(fname); QList resources = createResources(front); Q_FOREACH (PointerType resource, resources) { Q_CHECK_PTR(resource); if (resource->load() && resource->valid() && !resource->md5().isEmpty()) { addResourceToMd5Registry(resource); m_resourcesByFilename[resource->shortFilename()] = resource; if (resource->name().isEmpty()) { resource->setName(fname); } if (m_resourcesByName.contains(resource->name())) { resource->setName(resource->name() + "(" + resource->shortFilename() + ")"); } m_resourcesByName[resource->name()] = resource; notifyResourceAdded(resource); } else { warnWidgets << "Loading resource " << front << "failed." << type(); Policy::deleteResource(resource); } } m_loadLock.unlock(); } } m_resources = sortedResources(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTaggedResourceView(); } // qDebug() << "done loading resources for type " << type(); } void loadTags() { m_tagStore->loadTags(); } void clearOldSystemTags() { m_tagStore->clearOldSystemTags(); } /// Adds an already loaded resource to the server bool addResource(PointerType resource, bool save = true, bool infront = false) { if (!resource->valid()) { warnWidgets << "Tried to add an invalid resource!"; return false; } if (save) { QFileInfo fileInfo(resource->filename()); QDir d(fileInfo.path()); if (!d.exists()) { d.mkdir(fileInfo.path()); } if (fileInfo.exists()) { - QString filename = fileInfo.path() + "/" + fileInfo.baseName() + "XXXXXX" + "." + fileInfo.suffix(); + QString filename = fileInfo.path() + "/" + fileInfo.completeBaseName() + "XXXXXX" + "." + fileInfo.suffix(); debugWidgets << "fileName is " << filename; QTemporaryFile file(filename); if (file.open()) { debugWidgets << "now " << file.fileName(); resource->setFilename(file.fileName()); } } if (!resource->save()) { warnWidgets << "Could not save resource!"; return false; } } Q_ASSERT(!resource->filename().isEmpty() || !resource->name().isEmpty()); if (resource->filename().isEmpty()) { resource->setFilename(resource->name()); } else if (resource->name().isEmpty()) { resource->setName(resource->filename()); } m_resourcesByFilename[resource->shortFilename()] = resource; addResourceToMd5Registry(resource); m_resourcesByName[resource->name()] = resource; if (infront) { m_resources.insert(0, resource); } else { m_resources.append(resource); } notifyResourceAdded(resource); return true; } /** * Removes a given resource from the blacklist. */ bool removeFromBlacklist(PointerType resource) { if (m_blackListFileNames.contains(resource->filename())) { m_blackListFileNames.removeAll(resource->filename()); writeBlackListFile(); return true; } return false; } /// Remove a resource from Resource Server but not from a file bool removeResourceFromServer(PointerType resource){ if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) { return false; } removeResourceFromMd5Registry(resource); m_resourcesByName.remove(resource->name()); m_resourcesByFilename.remove(resource->shortFilename()); m_resources.removeAt(m_resources.indexOf(resource)); m_tagStore->removeResource(resource); notifyRemovingResource(resource); Policy::deleteResource(resource); return true; } /// Remove a resource from the resourceserver and blacklist it bool removeResourceAndBlacklist(PointerType resource) { if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) { return false; } removeResourceFromMd5Registry(resource); m_resourcesByName.remove(resource->name()); m_resourcesByFilename.remove(resource->shortFilename()); m_resources.removeAt(m_resources.indexOf(resource)); m_tagStore->removeResource(resource); notifyRemovingResource(resource); m_blackListFileNames.append(resource->filename()); writeBlackListFile(); Policy::deleteResource(resource); return true; } QList resources() { m_loadLock.lock(); QList resourceList = m_resources; Q_FOREACH (PointerType r, m_resourceBlackList) { resourceList.removeOne(r); } m_loadLock.unlock(); return resourceList; } /// Returns path where to save user defined and imported resources to virtual QString saveLocation() { return KoResourcePaths::saveLocation(type().toLatin1()); } /** * Creates a new resource from a given file and adds them to the resource server * The base implementation does only load one resource per file, override to implement collections * @param filename file name of the resource file to be imported * @param fileCreation decides whether to create the file in the saveLocation() directory */ virtual bool importResourceFile(const QString & filename , bool fileCreation=true) { QFileInfo fi(filename); if (!fi.exists()) return false; if ( fi.size() == 0) return false; PointerType resource = createResource( filename ); resource->load(); if (!resource->valid()) { warnWidgets << "Import failed! Resource is not valid"; Policy::deleteResource(resource); return false; } if (fileCreation) { Q_ASSERT(!resource->defaultFileExtension().isEmpty()); Q_ASSERT(!saveLocation().isEmpty()); - QString newFilename = saveLocation() + fi.baseName() + resource->defaultFileExtension(); + QString newFilename = saveLocation() + fi.completeBaseName() + resource->defaultFileExtension(); QFileInfo fileInfo(newFilename); int i = 1; while (fileInfo.exists()) { - fileInfo.setFile(saveLocation() + fi.baseName() + QString("%1").arg(i) + resource->defaultFileExtension()); + fileInfo.setFile(saveLocation() + fi.completeBaseName() + QString("%1").arg(i) + resource->defaultFileExtension()); i++; } resource->setFilename(fileInfo.filePath()); } if(!addResource(resource)) { Policy::deleteResource(resource); } return true; } /// Removes the resource file from the resource server void removeResourceFile(const QString & filename) { QFileInfo fi(filename); PointerType resource = resourceByFilename(fi.fileName()); if (!resource) { warnWidgets << "Resource file do not exist "; return; } removeResourceFromServer(resource); } /** * Addes an observer to the server * @param observer the observer to be added * @param notifyLoadedResources determines if the observer should be notified about the already loaded resources */ void addObserver(ObserverType* observer, bool notifyLoadedResources = true) { m_loadLock.lock(); if(observer && !m_observers.contains(observer)) { m_observers.append(observer); if(notifyLoadedResources) { Q_FOREACH (PointerType resource, m_resourcesByFilename) { observer->resourceAdded(resource); } } } m_loadLock.unlock(); } /** * Removes an observer from the server * @param observer the observer to be removed */ void removeObserver(ObserverType* observer) { int index = m_observers.indexOf( observer ); if( index < 0 ) return; m_observers.removeAt( index ); } PointerType resourceByFilename(const QString& filename) const { if (m_resourcesByFilename.contains(filename)) { return m_resourcesByFilename[filename]; } return 0; } PointerType resourceByName( const QString& name ) const { if (m_resourcesByName.contains(name)) { return m_resourcesByName[name]; } return 0; } PointerType resourceByMD5(const QByteArray& md5) const { return m_resourcesByMd5.value(md5); } /** * Call after changing the content of a resource; * Notifies the connected views. */ void updateResource( PointerType resource ) { notifyResourceChanged(resource); } QStringList blackListedFiles() override { if (type() == "kis_resourcebundles") { KConfigGroup group = KSharedConfig::openConfig()->group("BundleHack"); if (group.readEntry("HideKrita3Bundle", true)) { Q_FOREACH(const QString &filename, fileNames()) { if (filename.endsWith("Krita_3_Default_Resources.bundle")) { if (!m_blackListFileNames.contains(filename)) { m_blackListFileNames.append(filename); } } } } // qDebug() << "blacklisted filenames" << m_blackListFileNames; } return m_blackListFileNames; } void removeBlackListedFiles() { QStringList remainingFiles; // Files that can't be removed e.g. no rights will stay blacklisted Q_FOREACH (const QString &filename, m_blackListFileNames) { QFile file( filename ); if( ! file.remove() ) { remainingFiles.append(filename); } } m_blackListFileNames = remainingFiles; writeBlackListFile(); } QStringList tagNamesList() const { return m_tagStore->tagNamesList(); } // don't use these method directly since it doesn't update views! void addTag( KoResource* resource,const QString& tag) { m_tagStore->addTag(resource,tag); } // don't use these method directly since it doesn't update views! void delTag( KoResource* resource,const QString& tag) { m_tagStore->delTag(resource, tag); } QStringList searchTag(const QString& lineEditText) { return m_tagStore->searchTag(lineEditText); } void tagCategoryAdded(const QString& tag) { m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTagAddition(tag); } } void tagCategoryRemoved(const QString& tag) { m_tagStore->delTag(tag); m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTagRemoval(tag); } } void tagCategoryMembersChanged() { m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTaggedResourceView(); } } QStringList queryResources(const QString &query) const override { return m_tagStore->searchTag(query); } QStringList assignedTagsList(KoResource* resource) const { return m_tagStore->assignedTagsList(resource); } /** * Create one or more resources from a single file. By default one resource is created. * Override to create more resources from the file. * @param filename the filename of the resource or resource collection */ virtual QList createResources( const QString & filename ) { QList createdResources; createdResources.append(createResource(filename)); return createdResources; } virtual PointerType createResource( const QString & filename ) = 0; /// Return the currently stored resources in alphabetical order, overwrite for customized sorting virtual QList sortedResources() { QMap sortedNames; Q_FOREACH (const QString &name, m_resourcesByName.keys()) { sortedNames.insert(name.toLower(), m_resourcesByName[name]); } return sortedNames.values(); } protected: void notifyResourceAdded(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceAdded(resource); } } void notifyRemovingResource(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->removingResource(resource); } } void notifyResourceChanged(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceChanged(resource); } } /// Reads the xml file and returns the filenames as a list QStringList readBlackListFile() { QStringList filenameList; QFile f(m_blackListFile); if (!f.open(QIODevice::ReadOnly)) { return filenameList; } QDomDocument doc; if (!doc.setContent(&f)) { warnWidgets << "The file could not be parsed."; return filenameList; } QDomElement root = doc.documentElement(); if (root.tagName() != "resourceFilesList") { warnWidgets << "The file doesn't seem to be of interest."; return filenameList; } QDomElement file = root.firstChildElement("file"); while (!file.isNull()) { QDomNode n = file.firstChild(); QDomElement e = n.toElement(); if (e.tagName() == "name") { // If the krita bundle has landed in the blacklist, skip it. if (type() == "kis_resourcebundles") { // qDebug() << "Checking for not reading bundle" << e.text(); if (e.text().endsWith("Krita_3_Default_Resources.bundle")) { file = file.nextSiblingElement("file"); } } filenameList.append(e.text().replace(QString("~"), QDir::homePath())); } file = file.nextSiblingElement("file"); } // if (type() == "kis_resourcebundles") { // qDebug() << "Read bundle blacklist" << filenameList; // } return filenameList; } /// write the blacklist file entries to an xml file void writeBlackListFile() { QFile f(m_blackListFile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { warnWidgets << "Cannot write meta information to '" << m_blackListFile << "'." << endl; return; } QDomDocument doc; QDomElement root; QDomDocument docTemp("m_blackListFile"); doc = docTemp; doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); root = doc.createElement("resourceFilesList"); doc.appendChild(root); Q_FOREACH (QString filename, m_blackListFileNames) { // Don't write the krita3 bundle to the blacklist, since its location will change // when using the appimate. if (type() == "kis_resourcebundles") { // qDebug() << "Checking for Not writing krita 3 bundle" << filename; if (filename.endsWith("Krita_3_Default_Resources.bundle")) continue; } QDomElement fileEl = doc.createElement("file"); QDomElement nameEl = doc.createElement("name"); QDomText nameText = doc.createTextNode(filename.replace(QDir::homePath(), QString("~"))); nameEl.appendChild(nameText); fileEl.appendChild(nameEl); root.appendChild(fileEl); } QTextStream metastream(&f); metastream << doc.toString(); f.close(); } protected: KoResource* byMd5(const QByteArray &md5) const override { return Policy::toResourcePointer(resourceByMD5(md5)); } KoResource* byFileName(const QString &fileName) const override { return Policy::toResourcePointer(resourceByFilename(fileName)); } private: void addResourceToMd5Registry(PointerType resource) { const QByteArray md5 = resource->md5(); if (!md5.isEmpty()) { m_resourcesByMd5.insert(md5, resource); } } void removeResourceFromMd5Registry(PointerType resource) { const QByteArray md5 = resource->md5(); if (!md5.isEmpty()) { m_resourcesByMd5.remove(md5); } } private: QHash m_resourcesByName; QHash m_resourcesByFilename; QHash m_resourcesByMd5; QList m_resourceBlackList; QList m_resources; ///< list of resources in order of addition QList m_observers; QString m_blackListFile; KoResourceTagStore* m_tagStore; }; template > class KoResourceServerSimpleConstruction : public KoResourceServer { public: KoResourceServerSimpleConstruction(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) { } typename KoResourceServer::PointerType createResource( const QString & filename ) override { return new T(filename); } }; #endif // KORESOURCESERVER_H diff --git a/libs/widgets/KoResourceServerProvider.cpp b/libs/widgets/KoResourceServerProvider.cpp index f33db43416..1370a357a9 100644 --- a/libs/widgets/KoResourceServerProvider.cpp +++ b/libs/widgets/KoResourceServerProvider.cpp @@ -1,198 +1,199 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoResourcePaths.h" +#include "klocalizedstring.h" #include using namespace std; class GradientResourceServer : public KoResourceServer { public: GradientResourceServer(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) , m_foregroundToTransparent(0) , m_foregroundToBackground(0) { insertSpecialGradients(); } void insertSpecialGradients() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); QList stops; KoStopGradient* gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); - gradient->setName("Foreground to Transparent"); + gradient->setName(i18n("Foreground to Transparent")); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(QColor(0, 0, 0, 0), cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToTransparent = gradient; gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); - gradient->setName("Foreground to Background"); + gradient->setName(i18n("Foreground to Background")); stops.clear(); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(Qt::white, cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToBackground = gradient; } private: friend class KoResourceBundle; KoAbstractGradient* createResource( const QString & filename ) override { QString fileExtension; int index = filename.lastIndexOf('.'); if (index != -1) fileExtension = filename.mid(index).toLower(); KoAbstractGradient* grad = 0; if(fileExtension == ".svg" || fileExtension == ".kgr") grad = new KoStopGradient(filename); else if(fileExtension == ".ggr" ) grad = new KoSegmentGradient(filename); return grad; } QList< KoAbstractGradient* > sortedResources() override { QList< KoAbstractGradient* > resources = KoResourceServer::sortedResources(); QList< KoAbstractGradient* > sorted; if (m_foregroundToTransparent && resources.contains(m_foregroundToTransparent)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToTransparent))); } if (m_foregroundToBackground && resources.contains(m_foregroundToBackground)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToBackground))); } return sorted + resources; } KoAbstractGradient* m_foregroundToTransparent; KoAbstractGradient* m_foregroundToBackground; }; struct Q_DECL_HIDDEN KoResourceServerProvider::Private { KoResourceServer* patternServer; KoResourceServer* gradientServer; KoResourceServer* paletteServer; KoResourceServer *svgSymbolCollectionServer; KoResourceServer* gamutMaskServer; }; KoResourceServerProvider::KoResourceServerProvider() : d(new Private) { d->patternServer = new KoResourceServerSimpleConstruction("ko_patterns", "*.pat:*.jpg:*.gif:*.png:*.tif:*.xpm:*.bmp" ); d->patternServer->loadResources(blacklistFileNames(d->patternServer->fileNames(), d->patternServer->blackListedFiles())); d->gradientServer = new GradientResourceServer("ko_gradients", "*.svg:*.ggr"); d->gradientServer->loadResources(blacklistFileNames(d->gradientServer->fileNames(), d->gradientServer->blackListedFiles())); d->paletteServer = new KoResourceServerSimpleConstruction("ko_palettes", "*.kpl:*.gpl:*.pal:*.act:*.aco:*.css:*.colors:*.xml:*.sbz"); d->paletteServer->loadResources(blacklistFileNames(d->paletteServer->fileNames(), d->paletteServer->blackListedFiles())); d->svgSymbolCollectionServer = new KoResourceServerSimpleConstruction("symbols", "*.svg"); d->svgSymbolCollectionServer->loadResources(blacklistFileNames(d->svgSymbolCollectionServer->fileNames(), d->svgSymbolCollectionServer->blackListedFiles())); d->gamutMaskServer = new KoResourceServerSimpleConstruction("ko_gamutmasks", "*.kgm"); d->gamutMaskServer->loadResources(blacklistFileNames(d->gamutMaskServer->fileNames(), d->gamutMaskServer->blackListedFiles())); } KoResourceServerProvider::~KoResourceServerProvider() { delete d->patternServer; delete d->gradientServer; delete d->paletteServer; delete d->svgSymbolCollectionServer; delete d->gamutMaskServer; delete d; } Q_GLOBAL_STATIC(KoResourceServerProvider, s_instance) KoResourceServerProvider* KoResourceServerProvider::instance() { return s_instance; } QStringList KoResourceServerProvider::blacklistFileNames(QStringList fileNames, const QStringList &blacklistedFileNames) { if (!blacklistedFileNames.isEmpty()) { foreach (const QString &s, blacklistedFileNames) { fileNames.removeAll(s); } } return fileNames; } KoResourceServer* KoResourceServerProvider::patternServer() { return d->patternServer; } KoResourceServer* KoResourceServerProvider::gradientServer() { return d->gradientServer; } KoResourceServer* KoResourceServerProvider::paletteServer() { return d->paletteServer; } KoResourceServer *KoResourceServerProvider::svgSymbolCollectionServer() { return d->svgSymbolCollectionServer; } KoResourceServer* KoResourceServerProvider::gamutMaskServer() { return d->gamutMaskServer; } diff --git a/libs/widgets/KoTagChooserWidget.cpp b/libs/widgets/KoTagChooserWidget.cpp index 4321823bd3..f2b553ecfe 100644 --- a/libs/widgets/KoTagChooserWidget.cpp +++ b/libs/widgets/KoTagChooserWidget.cpp @@ -1,199 +1,202 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTagChooserWidget.h" #include #include #include #include #include #include #include "KoResourceItemChooserContextMenu.h" #include "KoTagToolButton.h" class Q_DECL_HIDDEN KoTagChooserWidget::Private { public: KisSqueezedComboBox* comboBox; KoTagToolButton* tagToolButton; QStringList readOnlyTags; QStringList tags; }; KoTagChooserWidget::KoTagChooserWidget(QWidget* parent): QWidget(parent) , d(new Private()) { d->comboBox = new KisSqueezedComboBox(this); d->comboBox->setToolTip(i18n("Tag")); d->comboBox->setSizePolicy(QSizePolicy::MinimumExpanding , QSizePolicy::Fixed ); - QGridLayout* comboLayout = new QGridLayout(this); comboLayout->addWidget(d->comboBox, 0, 0); d->tagToolButton = new KoTagToolButton(this); comboLayout->addWidget(d->tagToolButton, 0, 1); comboLayout->setSpacing(0); comboLayout->setMargin(0); comboLayout->setColumnStretch(0, 3); this->setEnabled(true); clear(); - connect(d->comboBox, SIGNAL(currentTextChanged(QString)), - this, SIGNAL(tagChosen(QString))); + connect(d->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(tagChanged(int))); connect(d->tagToolButton, SIGNAL(popupMenuAboutToShow()), this, SLOT (tagOptionsContextMenuAboutToShow())); connect(d->tagToolButton, SIGNAL(newTagRequested(QString)), this, SIGNAL(newTagRequested(QString))); connect(d->tagToolButton, SIGNAL(deletionOfCurrentTagRequested()), this, SLOT(contextDeleteCurrentTag())); connect(d->tagToolButton, SIGNAL(renamingOfCurrentTagRequested(QString)), this, SLOT(tagRenamingRequested(QString))); connect(d->tagToolButton, SIGNAL(undeletionOfTagRequested(QString)), this, SIGNAL(tagUndeletionRequested(QString))); connect(d->tagToolButton, SIGNAL(purgingOfTagUndeleteListRequested()), this, SIGNAL(tagUndeletionListPurgeRequested())); } KoTagChooserWidget::~KoTagChooserWidget() { delete d; } void KoTagChooserWidget::contextDeleteCurrentTag() { if (selectedTagIsReadOnly()) { return; } emit tagDeletionRequested(currentlySelectedTag()); } +void KoTagChooserWidget::tagChanged(int) +{ + emit tagChosen(d->comboBox->currentUnsqueezedText()); +} + void KoTagChooserWidget::tagRenamingRequested(const QString& newName) { if (newName.isEmpty() || selectedTagIsReadOnly()) { return; } emit tagRenamingRequested(currentlySelectedTag(), newName); } void KoTagChooserWidget::setUndeletionCandidate(const QString& tag) { d->tagToolButton->setUndeletionCandidate(tag); } void KoTagChooserWidget::setCurrentIndex(int index) { d->comboBox->setCurrentIndex(index); } int KoTagChooserWidget::findIndexOf(QString tagName) { return d->comboBox->findOriginalText(tagName); } void KoTagChooserWidget::addReadOnlyItem(QString tagName) { d->readOnlyTags.append(tagName); } void KoTagChooserWidget::insertItem(QString tagName) { QStringList tags = allTags(); tags.append(tagName); tags.sort(); foreach (QString readOnlyTag, d->readOnlyTags) { tags.prepend(readOnlyTag); } int index = tags.indexOf(tagName); if (d->comboBox->findOriginalText(tagName) == -1) { d->comboBox->insertSqueezedItem(tagName, index); d->tags.append(tagName); } } QString KoTagChooserWidget::currentlySelectedTag() { - return d->comboBox->itemHighlighted(); + return d->comboBox->currentUnsqueezedText(); } QStringList KoTagChooserWidget::allTags() { return d->tags; } bool KoTagChooserWidget::selectedTagIsReadOnly() { - return d->readOnlyTags.contains(d->comboBox->itemHighlighted()) ; + return d->readOnlyTags.contains(d->comboBox->currentUnsqueezedText()) ; } void KoTagChooserWidget::addItems(QStringList tagNames) { d->tags.append(tagNames); d->tags.removeDuplicates(); d->tags.sort(); d->readOnlyTags.sort(); QStringList items = d->readOnlyTags + d->tags; items.removeDuplicates(); d->comboBox->resetOriginalTexts(items); } void KoTagChooserWidget::clear() { d->comboBox->resetOriginalTexts(QStringList()); } void KoTagChooserWidget::removeItem(QString item) { int pos = findIndexOf(item); if (pos >= 0) { d->comboBox->removeSqueezedItem(pos); d->tags.removeOne(item); } } void KoTagChooserWidget::tagOptionsContextMenuAboutToShow() { /* only enable the save button if the selected tag set is editable */ d->tagToolButton->readOnlyMode(selectedTagIsReadOnly()); emit popupMenuAboutToShow(); } void KoTagChooserWidget::showTagToolButton(bool show) { d->tagToolButton->setVisible(show); } diff --git a/libs/widgets/KoTagChooserWidget.h b/libs/widgets/KoTagChooserWidget.h index 4a9419a896..04e57577db 100644 --- a/libs/widgets/KoTagChooserWidget.h +++ b/libs/widgets/KoTagChooserWidget.h @@ -1,73 +1,74 @@ /* * 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. */ #ifndef KOTAGCHOOSERWIDGET_H #define KOTAGCHOOSERWIDGET_H #include #include "kritawidgets_export.h" class KRITAWIDGETS_EXPORT KoTagChooserWidget : public QWidget { Q_OBJECT public: explicit KoTagChooserWidget(QWidget* parent); ~KoTagChooserWidget() override; void setCurrentIndex(int index); int findIndexOf(QString tagName); void insertItem(QString tagName); QString currentlySelectedTag(); QStringList allTags(); bool selectedTagIsReadOnly(); void removeItem(QString item); void addItems(QStringList tagNames); void addReadOnlyItem(QString tagName); void clear(); void setUndeletionCandidate(const QString &tag); void showTagToolButton(bool show); Q_SIGNALS: void newTagRequested(const QString &tagname); void tagDeletionRequested(const QString &tagname); void tagRenamingRequested(const QString &oldTagname, const QString &newTagname); void tagUndeletionRequested(const QString &tagname); void tagUndeletionListPurgeRequested(); void popupMenuAboutToShow(); void tagChosen(const QString &tag); private Q_SLOTS: void tagRenamingRequested(const QString &newName); void tagOptionsContextMenuAboutToShow(); void contextDeleteCurrentTag(); + void tagChanged(int index); private: class Private; Private* const d; }; ; #endif // KOTAGCHOOSERWIDGET_H diff --git a/libs/widgetutils/KisSqueezedComboBox.cpp b/libs/widgetutils/KisSqueezedComboBox.cpp index 51df44adc0..f644859e4c 100644 --- a/libs/widgetutils/KisSqueezedComboBox.cpp +++ b/libs/widgetutils/KisSqueezedComboBox.cpp @@ -1,180 +1,180 @@ /* ============================================================ * Author: Tom Albers * Date : 2005-01-01 * Description : * * Copyright 2005 by Tom Albers * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option)* any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "KisSqueezedComboBox.h" /** @file KisSqueezedComboBox.cpp */ // Qt includes. #include #include #include #include #include #include KisSqueezedComboBox::KisSqueezedComboBox(QWidget *parent, const char *name) : QComboBox(parent) { setObjectName(name); setMinimumWidth(100); m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimeOut())); } KisSqueezedComboBox::~KisSqueezedComboBox() { delete m_timer; } bool KisSqueezedComboBox::contains(const QString& _text) const { if (_text.isEmpty()) return false; for (QMap::const_iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { if (it.value() == _text) { return true; } } return false; } qint32 KisSqueezedComboBox::findOriginalText(const QString& text) const { for (int i = 0; i < m_originalItems.size(); i++) { if(m_originalItems.value(i) == text) { return i; } } return -1; } QStringList KisSqueezedComboBox::originalTexts() const { return m_originalItems.values(); } void KisSqueezedComboBox::resetOriginalTexts(const QStringList &texts) { if (texts == m_originalItems.values()) return; clear(); m_originalItems.clear(); Q_FOREACH (const QString &item, texts) { addSqueezedItem(item); } } QSize KisSqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18; int maxH = qMax(fm.lineSpacing(), 14) + 2; QStyleOptionComboBox options; options.initFrom(this); return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); } void KisSqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem, this), userData); } void KisSqueezedComboBox::insertSqueezedItem(const QIcon &icon, const QString &newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, icon, squeezeText(newItem, this), userData); } void KisSqueezedComboBox::addSqueezedItem(const QString& newItem, QVariant userData) { insertSqueezedItem(newItem, count(), userData); } void KisSqueezedComboBox::addSqueezedItem(const QIcon &icon, const QString &newItem, QVariant userData) { insertSqueezedItem(icon, newItem, count(), userData); } void KisSqueezedComboBox::setCurrent(const QString& itemText) { qint32 itemIndex = findOriginalText(itemText); if (itemIndex >= 0) { setCurrentIndex(itemIndex); } } void KisSqueezedComboBox::resizeEvent(QResizeEvent *) { m_timer->start(200); } void KisSqueezedComboBox::slotTimeOut() { for (QMap::iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { setItemText(it.key(), squeezeText(it.value(), this)); } } QString KisSqueezedComboBox::squeezeText(const QString& original, const QWidget *widget) { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = widget->width() - 30; QFontMetrics fm(widget->fontMetrics()); // If we can fit the full text, return that. if (fm.width(original) < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize - fm.width("..."); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.width(original.right(i)) > widgetSize) { sqItem = QString("..." + original.right(--i)); break; } } return sqItem; } -QString KisSqueezedComboBox::itemHighlighted() +QString KisSqueezedComboBox::currentUnsqueezedText() { int curItem = currentIndex(); return m_originalItems[curItem]; } void KisSqueezedComboBox::removeSqueezedItem(int index) { removeItem(index); m_originalItems.remove(index); } diff --git a/libs/widgetutils/KisSqueezedComboBox.h b/libs/widgetutils/KisSqueezedComboBox.h index 77db40aa43..0a3adf2b39 100644 --- a/libs/widgetutils/KisSqueezedComboBox.h +++ b/libs/widgetutils/KisSqueezedComboBox.h @@ -1,160 +1,160 @@ /* ============================================================ * Author: Tom Albers * Date : 2005-01-01 * Description : * * Copyright 2005 by Tom Albers * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef KISSQUEEZEDCOMBOBOX_H #define KISSQUEEZEDCOMBOBOX_H class QTimer; class QResizeEvent; class QWidget; // Qt includes. #include #include #include #include "kritawidgetutils_export.h" /** @class KisSqueezedComboBox * * This widget is a QComboBox, but then a little bit * different. It only shows the right part of the items * depending on de size of the widget. When it is not * possible to show the complete item, it will be shortened * and "..." will be prepended. * * @image html KisSqueezedComboBox.png "This is how it looks" * @author Tom Albers */ class KRITAWIDGETUTILS_EXPORT KisSqueezedComboBox : public QComboBox { Q_OBJECT public: /** * Constructor * @param parent parent widget * @param name name to give to the widget */ KisSqueezedComboBox(QWidget *parent = 0, const char *name = 0); /** * destructor */ ~KisSqueezedComboBox() override; /** * * Returns true if the combobox contains the original (not-squeezed) * version of text. * @param text the original (not-squeezed) text to check for */ bool contains(const QString & text) const; /** * Returns index of a original text, -1 if the text isn't found * @param text the original (not-squeezed) text to search for */ qint32 findOriginalText(const QString & text) const; /** * Return the list of original text items */ QStringList originalTexts() const; /** * Reset the combo box and initialize it with the list of (original) text items */ void resetOriginalTexts(const QStringList &texts); /** * This inserts a item to the list. See QComboBox::insertItem() * for details. Please do not use QComboBox::insertItem() to this * widget, as that will fail. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param index the position in the widget. * @param userData the user data. */ void insertSqueezedItem(const QString& newItem, int index, QVariant userData = QVariant()); void insertSqueezedItem(const QIcon &icon, const QString& newItem, int index, QVariant userData = QVariant()); /** * Append an item. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param userData the user data. */ void addSqueezedItem(const QString& newItem, QVariant userData = QVariant()); /** * Append an item. * @param icon the item icon * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param userData the user data */ void addSqueezedItem(const QIcon &icon, const QString& newItem, QVariant userData = QVariant()); /** * Set the current item to the one matching the given text. * * @param itemText the original (long version) of the item text */ void setCurrent(const QString& itemText); /** * This method returns the full text (not squeezed) of the currently * highlighted item. * @return full text of the highlighted item */ - QString itemHighlighted(); + QString currentUnsqueezedText(); /** * remove the squeezed item at index */ void removeSqueezedItem(int index); /** * Sets the sizeHint() of this widget. */ QSize sizeHint() const override; static QString squeezeText(const QString& original, const QWidget *widget); private Q_SLOTS: void slotTimeOut(); private: void resizeEvent(QResizeEvent *) override; // Prevent these from being used. void setCurrentText(const QString& itemText); void insertItem(const QString &text); void insertItem(qint32 index, const QString &text); void addItem(const QString &text); QMap m_originalItems; QTimer *m_timer; }; #endif // SQUEEZEDCOMBOBOX_H diff --git a/libs/widgetutils/KoFileDialog.cpp b/libs/widgetutils/KoFileDialog.cpp index 024a8b1a25..57c3c1dc56 100644 --- a/libs/widgetutils/KoFileDialog.cpp +++ b/libs/widgetutils/KoFileDialog.cpp @@ -1,432 +1,432 @@ /* This file is part of the KDE project Copyright (C) 2013 - 2014 Yue Liu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; 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 "KoFileDialog.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoFileDialog::Private { public: Private(QWidget *parent_, KoFileDialog::DialogType dialogType_, const QString caption_, const QString defaultDir_, const QString dialogName_) : parent(parent_) , type(dialogType_) , dialogName(dialogName_) , caption(caption_) , defaultDirectory(defaultDir_) , filterList(QStringList()) , defaultFilter(QString()) , swapExtensionOrder(false) { } ~Private() { } QWidget *parent; KoFileDialog::DialogType type; QString dialogName; QString caption; QString defaultDirectory; QString proposedFileName; QStringList filterList; QString defaultFilter; QScopedPointer fileDialog; QString mimeType; bool swapExtensionOrder; }; KoFileDialog::KoFileDialog(QWidget *parent, KoFileDialog::DialogType type, const QString &dialogName) : d(new Private(parent, type, "", getUsedDir(dialogName), dialogName)) { } KoFileDialog::~KoFileDialog() { delete d; } void KoFileDialog::setCaption(const QString &caption) { d->caption = caption; } void KoFileDialog::setDefaultDir(const QString &defaultDir, bool force) { if (!defaultDir.isEmpty()) { if (d->defaultDirectory.isEmpty() || force) { QFileInfo f(defaultDir); if (f.isDir()) { d->defaultDirectory = defaultDir; } else { d->defaultDirectory = f.absolutePath(); } } if (!QFileInfo(defaultDir).isDir()) { d->proposedFileName = QFileInfo(defaultDir).fileName(); } } } void KoFileDialog::setImageFilters() { QStringList imageFilters; // add filters for all formats supported by QImage Q_FOREACH (const QByteArray &format, QImageReader::supportedImageFormats()) { imageFilters << QLatin1String("image/") + format; } setMimeTypeFilters(imageFilters); } void KoFileDialog::setMimeTypeFilters(const QStringList &mimeTypeList, QString defaultMimeType) { d->filterList = getFilterStringListFromMime(mimeTypeList, true); QString defaultFilter; if (!defaultMimeType.isEmpty()) { QString suffix = KisMimeDatabase::suffixesForMimeType(defaultMimeType).first(); if (!d->proposedFileName.isEmpty()) { - d->proposedFileName = QFileInfo(d->proposedFileName).baseName() + "." + suffix; + d->proposedFileName = QFileInfo(d->proposedFileName).completeBaseName() + "." + suffix; } QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultMimeType, false); if (defaultFilters.size() > 0) { defaultFilter = defaultFilters.first(); } } d->defaultFilter = defaultFilter; } QString KoFileDialog::selectedNameFilter() const { return d->fileDialog->selectedNameFilter(); } QString KoFileDialog::selectedMimeType() const { return d->mimeType; } void KoFileDialog::createFileDialog() { d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory + "/" + d->proposedFileName)); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif d->fileDialog->setOption(QFileDialog::DontUseNativeDialog, group.readEntry("DontUseNativeFileDialog", dontUseNative)); d->fileDialog->setOption(QFileDialog::DontConfirmOverwrite, false); d->fileDialog->setOption(QFileDialog::HideNameFilterDetails, true); #ifdef Q_OS_MACOS QList urls = d->fileDialog->sidebarUrls(); QUrl volumes = QUrl::fromLocalFile("/Volumes"); if (!urls.contains(volumes)) { urls.append(volumes); } d->fileDialog->setSidebarUrls(urls); #endif if (d->type == SaveFile) { d->fileDialog->setAcceptMode(QFileDialog::AcceptSave); d->fileDialog->setFileMode(QFileDialog::AnyFile); } else { // open / import d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen); if (d->type == ImportDirectory || d->type == OpenDirectory){ d->fileDialog->setFileMode(QFileDialog::Directory); d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true); } else { // open / import file(s) if (d->type == OpenFile || d->type == ImportFile) { d->fileDialog->setFileMode(QFileDialog::ExistingFile); } else { // files d->fileDialog->setFileMode(QFileDialog::ExistingFiles); } } } d->fileDialog->setNameFilters(d->filterList); if (!d->proposedFileName.isEmpty()) { QString mime = KisMimeDatabase::mimeTypeForFile(d->proposedFileName, d->type == KoFileDialog::SaveFile ? false : true); QString description = KisMimeDatabase::descriptionForMimeType(mime); Q_FOREACH(const QString &filter, d->filterList) { if (filter.startsWith(description)) { d->fileDialog->selectNameFilter(filter); break; } } } else if (!d->defaultFilter.isEmpty()) { d->fileDialog->selectNameFilter(d->defaultFilter); } if (d->type == ImportDirectory || d->type == ImportFile || d->type == ImportFiles || d->type == SaveFile) { d->fileDialog->setWindowModality(Qt::WindowModal); } } QString KoFileDialog::filename() { QString url; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { url = d->fileDialog->selectedFiles().first(); } if (!url.isEmpty()) { QString suffix = QFileInfo(url).suffix(); if (KisMimeDatabase::mimeTypeForSuffix(suffix).isEmpty()) { suffix = ""; } if (d->type == SaveFile && suffix.isEmpty()) { QString selectedFilter; // index 0 is all supported; if that is chosen, saveDocument will automatically make it .kra for (int i = 1; i < d->filterList.size(); ++i) { if (d->filterList[i].startsWith(d->fileDialog->selectedNameFilter())) { selectedFilter = d->filterList[i]; break; } } int start = selectedFilter.indexOf("*.") + 1; int end = selectedFilter.indexOf(" ", start); int n = end - start; QString extension = selectedFilter.mid(start, n); if (!(extension.contains(".") || url.endsWith("."))) { extension = "." + extension; } url = url + extension; } d->mimeType = KisMimeDatabase::mimeTypeForFile(url, d->type == KoFileDialog::SaveFile ? false : true); saveUsedDir(url, d->dialogName); } return url; } QStringList KoFileDialog::filenames() { QStringList urls; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { urls = d->fileDialog->selectedFiles(); } if (urls.size() > 0) { saveUsedDir(urls.first(), d->dialogName); } return urls; } QStringList KoFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList) { Q_ASSERT(mimeList); QStringList filters; QString description; if (nameFilter.contains("(")) { description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed(); } QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts ); entries.sort(); Q_FOREACH (QString entry, entries) { entry = entry.remove("*"); entry = entry.remove(")"); QString mimeType = KisMimeDatabase::mimeTypeForSuffix(entry); if (mimeType != "application/octet-stream") { if (!mimeList->contains(mimeType)) { mimeList->append(mimeType); filters.append(KisMimeDatabase::descriptionForMimeType(mimeType) + " ( *" + entry + " )"); } } else { filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )"); } } return filters; } const QStringList KoFileDialog::getFilterStringListFromMime(const QStringList &_mimeList, bool withAllSupportedEntry) { QStringList mimeSeen; // 1 QString allSupported; // 2 QString kritaNative; // 3 QString ora; QStringList ret; QStringList mimeList = _mimeList; mimeList.sort(); Q_FOREACH(const QString &mimeType, mimeList) { if (!mimeSeen.contains(mimeType)) { QString description = KisMimeDatabase::descriptionForMimeType(mimeType); if (description.isEmpty() && !mimeType.isEmpty()) { description = mimeType.split("/")[1]; if (description.startsWith("x-")) { description = description.remove(0, 2); } } QString oneFilter; QStringList patterns = KisMimeDatabase::suffixesForMimeType(mimeType); QStringList globPatterns; Q_FOREACH(const QString &pattern, patterns) { if (pattern.startsWith(".")) { globPatterns << "*" + pattern; } else if (pattern.startsWith("*.")) { globPatterns << pattern; } else { globPatterns << "*." + pattern; } } Q_FOREACH(const QString &glob, globPatterns) { if (d->swapExtensionOrder) { oneFilter.prepend(glob + " "); if (withAllSupportedEntry) { allSupported.prepend(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.prepend(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.prepend(glob.toUpper() + " "); } } #endif } else { oneFilter.append(glob + " "); if (withAllSupportedEntry) { allSupported.append(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.append(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.append(glob.toUpper() + " "); } } #endif } } Q_ASSERT(!description.isEmpty()); oneFilter = description + " ( " + oneFilter + ")"; if (mimeType == "application/x-krita") { kritaNative = oneFilter; continue; } if (mimeType == "image/openraster") { ora = oneFilter; continue; } else { ret << oneFilter; } mimeSeen << mimeType; } } ret.sort(); ret.removeDuplicates(); if (!ora.isEmpty()) ret.prepend(ora); if (!kritaNative.isEmpty()) ret.prepend(kritaNative); if (!allSupported.isEmpty()) ret.prepend(i18n("All supported formats") + " ( " + allSupported + (")")); return ret; } QString KoFileDialog::getUsedDir(const QString &dialogName) { if (dialogName.isEmpty()) return ""; KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString dir = group.readEntry(dialogName, ""); return dir; } void KoFileDialog::saveUsedDir(const QString &fileName, const QString &dialogName) { if (dialogName.isEmpty()) return; QFileInfo fileInfo(fileName); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry(dialogName, fileInfo.absolutePath()); } diff --git a/libs/widgetutils/config/kcolorschememanager.cpp b/libs/widgetutils/config/kcolorschememanager.cpp index fcd64f63c6..70ff8e4661 100644 --- a/libs/widgetutils/config/kcolorschememanager.cpp +++ b/libs/widgetutils/config/kcolorschememanager.cpp @@ -1,202 +1,202 @@ /* This file is part of the KDE project * Copyright (C) 2013 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 "kcolorschememanager.h" #include "kcolorschememanager_p.h" #include #include #include #include #include #include #include #include #include KColorSchemeManagerPrivate::KColorSchemeManagerPrivate() : model(new KColorSchemeModel()) { } KColorSchemeModel::KColorSchemeModel(QObject *parent) : QAbstractListModel(parent) { init(); } KColorSchemeModel::~KColorSchemeModel() { } int KColorSchemeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_data.count(); } QVariant KColorSchemeModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || (index.row() >= m_data.count())) { return QVariant(); } switch (role) { case Qt::DisplayRole: return m_data.at(index.row()).name; case Qt::DecorationRole: return m_data.at(index.row()).preview; case Qt::UserRole: return m_data.at(index.row()).path; default: return QVariant(); } } void KColorSchemeModel::init() { beginResetModel(); m_data.clear(); const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory); QStringList schemeFiles; Q_FOREACH (const QString &dir, dirs) { const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.colors")); Q_FOREACH (const QString &file, fileNames) { schemeFiles << dir + QDir::separator() + file; } } Q_FOREACH (const QString &schemeFile, schemeFiles) { KSharedConfigPtr config = KSharedConfig::openConfig(schemeFile); KConfigGroup group(config, QStringLiteral("General")); - const QString name = group.readEntry("Name", QFileInfo(schemeFile).baseName()); + const QString name = group.readEntry("Name", QFileInfo(schemeFile).completeBaseName()); const KColorSchemeModelData data = {name, schemeFile, createPreview(schemeFile)}; m_data.append(data); } std::sort(m_data.begin(), m_data.end(), [](const KColorSchemeModelData & first, const KColorSchemeModelData & second) { return first.name < second.name; }); endResetModel(); } QIcon KColorSchemeModel::createPreview(const QString &path) { KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path); QIcon result; KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig); KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig); KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig); KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig); auto pixmap = [&](int size) { QPixmap pix(size, size); pix.fill(Qt::black); QPainter p; p.begin(&pix); const int itemSize = size / 2 - 1; p.fillRect(1, 1, itemSize, itemSize, activeWindow.background()); p.fillRect(1 + itemSize, 1, itemSize, itemSize, activeButton.background()); p.fillRect(1, 1 + itemSize, itemSize, itemSize, activeView.background()); p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, activeSelection.background()); p.end(); result.addPixmap(pix); }; // 16x16 pixmap(16); // 24x24 pixmap(24); return result; } KColorSchemeManager::KColorSchemeManager(QObject *parent) : QObject(parent) , d(new KColorSchemeManagerPrivate) { } KColorSchemeManager::~KColorSchemeManager() { } QAbstractItemModel *KColorSchemeManager::model() const { return d->model.data(); } QModelIndex KColorSchemeManager::indexForScheme(const QString &name) const { for (int i = 0; i < d->model->rowCount(); ++i) { QModelIndex index = d->model->index(i); if (index.data().toString() == name) { return index; } } return QModelIndex(); } KActionMenu *KColorSchemeManager::createSchemeSelectionMenu(const QIcon &icon, const QString &name, const QString &selectedSchemeName, QObject *parent) { KActionMenu *menu = new KActionMenu(icon, name, parent); QActionGroup *group = new QActionGroup(menu); connect(group, &QActionGroup::triggered, [](QAction * action) { // hint for the style to synchronize the color scheme with the window manager/compositor qApp->setProperty("KDE_COLOR_SCHEME_PATH", action->data()); qApp->setPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig(action->data().toString()))); }); for (int i = 0; i < d->model->rowCount(); ++i) { QModelIndex index = d->model->index(i); QAction *action = new QAction(index.data(Qt::DecorationRole).value(), index.data().toString(), menu); action->setData(index.data(Qt::UserRole)); action->setActionGroup(group); action->setCheckable(true); if (index.data().toString() == selectedSchemeName) { action->setChecked(true); } menu->addAction(action); } return menu; } KActionMenu *KColorSchemeManager::createSchemeSelectionMenu(const QString &text, const QString &selectedSchemeName, QObject *parent) { return createSchemeSelectionMenu(QIcon(), text, selectedSchemeName, parent); } KActionMenu *KColorSchemeManager::createSchemeSelectionMenu(const QString &selectedSchemeName, QObject *parent) { return createSchemeSelectionMenu(QIcon(), QString(), selectedSchemeName, parent); } void KColorSchemeManager::activateScheme(const QModelIndex &index) { if (!index.isValid()) { return; } if (index.model() != d->model.data()) { return; } // hint for the style to synchronize the color scheme with the window manager/compositor qApp->setProperty("KDE_COLOR_SCHEME_PATH", index.data(Qt::UserRole)); qApp->setPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig(index.data(Qt::UserRole).toString()))); } diff --git a/packaging/linux/appimage/build-krita.sh b/packaging/linux/appimage/build-krita.sh index 84288455e0..f94c56db23 100755 --- a/packaging/linux/appimage/build-krita.sh +++ b/packaging/linux/appimage/build-krita.sh @@ -1,52 +1,52 @@ #!/bin/bash # Halt on errors and be verbose about what we are doing set -e set -x # Read in our parameters export BUILD_PREFIX=$1 export KRITA_SOURCES=$2 # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. # That's not always the case, so make sure it is export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # We want to use $prefix/deps/usr/ for all our dependencies export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/ export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/ # Setup variables needed to help everything find what we build export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH export PATH=$DEPS_INSTALL_PREFIX/bin:$PATH export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig:$DEPS_INSTALL_PREFIX/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH export PYTHONPATH=$DEPS_INSTALL_PREFIX/sip:$DEPS_INSTALL_PREFIX/lib/python3.5/site-packages:$DEPS_INSTALL_PREFIX/lib/python3.5 export PYTHONHOME=$DEPS_INSTALL_PREFIX # Make sure our build directory exists if [ ! -d $BUILD_PREFIX/krita-build/ ] ; then mkdir -p $BUILD_PREFIX/krita-build/ fi # Now switch to it cd $BUILD_PREFIX/krita-build/ # Determine how many CPUs we have CPU_COUNT=`grep processor /proc/cpuinfo | wc -l` # Configure Krita cmake $KRITA_SOURCES \ -DCMAKE_INSTALL_PREFIX:PATH=$BUILD_PREFIX/krita.appdir/usr \ -DDEFINE_NO_DEPRECATED=1 \ -DCMAKE_BUILD_TYPE=Release \ -DFOUNDATION_BUILD=1 \ - -DHIDE_SAFE_ASSERTS=ON \ + -DHIDE_SAFE_ASSERTS=OFF \ -DBUILD_TESTING=FALSE \ -DPYQT_SIP_DIR_OVERRIDE=$DEPS_INSTALL_PREFIX/share/sip/ \ -DHAVE_MEMORY_LEAK_TRACKER=FALSE # Build and Install Krita (ready for the next phase) make -j$CPU_COUNT install diff --git a/packaging/linux/flatpak/FLATPAK.md b/packaging/linux/flatpak/FLATPAK.md index adcfd4b7bd..613514777a 100644 --- a/packaging/linux/flatpak/FLATPAK.md +++ b/packaging/linux/flatpak/FLATPAK.md @@ -1,22 +1,18 @@ Krita Flatpak ------------- 1. add the flathub repository: `$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo` -2. install the KDE SDK from flathub: +2. compile krita and install it into a local repository: -`$ flatpak install flathub org.kde.Sdk` +`$ flatpak-builder --repo=repo_dir --install-deps-from=flathub --force-clean build_dir org.kde.krita.yaml` -3. compile krita and install it into a local repository: +3. export krita from the local repository to a bundle: -`$ flatpak-builder --repo=repo_dir --force-clean build_dir org.kde.krita-nightly.json` +`$ flatpak build-bundle repo_dir krita--x86_64.flatpak org.kde.krita master` -4. export krita from the local repository to a bundle: +4. install the bundle: -`$ flatpak build-bundle repo_dir krita-nightly-x86_64.flatpak org.kde.krita master` - -5. install the bundle: - -`$ flatpak install krita-nightly-x86_64.flatpak` +`$ flatpak install krita-x86_64.flatpak` diff --git a/packaging/linux/flatpak/org.kde.krita-nightly.yaml b/packaging/linux/flatpak/org.kde.krita-nightly.yaml new file mode 100644 index 0000000000..537e948a14 --- /dev/null +++ b/packaging/linux/flatpak/org.kde.krita-nightly.yaml @@ -0,0 +1,278 @@ +app-id: org.kde.krita-stable +default-branch: stable +runtime: org.kde.Platform +runtime-version: '5.12' +sdk: org.kde.Sdk +command: krita +rename-icon: calligrakrita +finish-args: + - --share=ipc + - --socket=x11 + - --share=network + - --device=dri + - --socket=pulseaudio + - --filesystem=host + - --filesystem=xdg-config/kdeglobals:ro + - --env=PYTHONPATH=/app/lib/python3/dist-packages + - --env=TMPDIR=/var/tmp +cleanup: + - /include + - /lib/pkgconfig + - /lib/cmake + - /share/aclocal + - /share/pkgconfig + - /share/info + - /share/man + - /cmake + - '*.a' + - '*.la' + - '*.cmake' +modules: + - name: sip + buildsystem: simple + build-commands: + - python3 configure.py + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --incdir=/app/include/python3 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages + --sip-module=PyQt5.sip + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/sip/4.19.17/sip-4.19.17.tar.gz + sha256: 12bcd8f4d5feefc105bc075d12c5090ee783f7380728563c91b8b95d0ec45df3 + + - name: pyqt + buildsystem: simple + build-commands: + - python3 configure.py + --confirm-license + --sip-incdir=/app/include/python3 + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --designer-plugindir=/app/lib/plugins/designer + --qml-plugindir=/app/lib/plugins/PyQt5 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages/PyQt5 + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.2/PyQt5_gpl-5.12.2.tar.gz + sha256: c565829e77dc9c281aa1a0cdf2eddaead4e0f844cbaf7a4408441967f03f5f0f + + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=/app --with-libraries=system + - ./b2 -j $FLATPAK_BUILDER_N_JOBS install + sources: + - type: archive + url: https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2 + sha256: 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406 + + - name: eigen + buildsystem: cmake-ninja + builddir: true + cleanup: + - '*' + sources: + - type: archive + url: https://bitbucket.org/eigen/eigen/get/3.3.7.tar.bz2 + sha256: 9f13cf90dedbe3e52a19f43000d71fdf72e986beb9a5436dddcd61ff9d77a3ce + + - name: quazip + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://github.com/stachenov/quazip/archive/0.7.6.tar.gz + sha256: 4118a830a375a81211956611cc34b1b5b4ddc108c126287b91b40c2493046b70 + - type: shell + commands: + - sed -i 's|${CMAKE_ROOT}/Modules|share/cmake|' CMakeLists.txt + + - name: exiv2 + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - /bin + sources: + - type: archive + url: https://www.exiv2.org/builds/exiv2-0.27.1-Source.tar.gz + sha256: f125286980fd1bcb28e188c02a93946951c61e10784720be2301b661a65b3081 + + - name: ilmbase + config-opts: + - --disable-static + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz + sha256: 456978d1a978a5f823c7c675f3f36b0ae14dba36638aeaa3c4b0e784f12a3862 + + - name: openexr + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz + sha256: fd6cb3a87f8c1a233be17b94c74799e6241d50fc5efd4df75c7a4b9cf4e25ea6 + + - name: libraw + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://www.libraw.org/data/LibRaw-0.19.2.tar.gz + sha256: 400d47969292291d297873a06fb0535ccce70728117463927ddd9452aa849644 + + - name: opencolorio + buildsystem: cmake # ninja build broken (fixed in 2.0) + builddir: true + build-options: + arch: + arm: + config-opts: + - -DOCIO_USE_SSE=OFF + aarch64: + config-opts: + - -DOCIO_USE_SSE=OFF + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DOCIO_BUILD_STATIC=OFF + - -DCMAKE_CXX_FLAGS='-Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-error=cast-function-type' + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/imageworks/OpenColorIO/archive/v1.1.1.tar.gz + sha256: c9b5b9def907e1dafb29e37336b702fff22cc6306d445a13b1621b8a754c14c8 + + - name: vc + skip-arches: + - aarch64 + - arm + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz + sha256: 08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe + - type: shell + commands: + - sed -i 's/x86|/x86|i686|/' CMakeLists.txt + + - name: poppler-data + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-data-0.4.9.tar.gz + sha256: 1f9c7e7de9ecd0db6ab287349e31bf815ca108a5a175cf906a90163bdbe32012 + + - name: poppler + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_GTK_TESTS=OFF + - -DBUILD_QT5_TESTS=OFF + - -DBUILD_CPP_TESTS=OFF + - -DENABLE_UTILS=OFF + - -DENABLE_CPP=OFF + - -DENABLE_GLIB=OFF + - -DENABLE_LIBOPENJPEG=none + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-0.77.0.tar.xz + sha256: 7267eb4cbccd64a58244b8211603c1c1b6bf32c7f6a4ced2642865346102f36b + + - name: gsl + config-opts: + - --disable-static + cleanup: + - /bin + sources: + - type: archive + url: https://ftpmirror.gnu.org/gnu/gsl/gsl-2.5.tar.gz + sha256: 0460ad7c2542caaddc6729762952d345374784100223995eb14d614861f2258d + + - name: gmic-qt + buildsystem: cmake-ninja + builddir: true + subdir: gmic-qt + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DGMIC_QT_HOST=krita + - -DGMIC_PATH=../src + - -DCMAKE_CXX_FLAGS=-lfftw3_threads + sources: + - type: archive + url: https://gmic.eu/files/source/gmic_2.6.4.tar.gz + sha256: 4cd88b2dca6b9b1a330ab4556d36656bafb98e4e9814bf0448545b27ef18dae3 + + - name: x264 + config-opts: + - --disable-cli + - --enable-shared + sources: + - type: archive + url: https://download.videolan.org/x264/snapshots/x264-snapshot-20190305-2245-stable.tar.bz2 + sha256: be52c96ef8bd930fbc1ecff03abac9b94976b444ea7641345e08e20d9e594d16 + + - name: ffmpeg + config-opts: + - --enable-rpath + - --enable-gpl + - --disable-static + - --enable-shared + - --disable-doc + - --disable-ffplay + - --disable-ffprobe + - --enable-libopus + - --enable-libvpx + - --enable-libx264 + cleanup: + - /share/ffmpeg/examples + sources: + - type: archive + url: https://www.ffmpeg.org/releases/ffmpeg-4.1.4.tar.xz + sha256: f1f049a82fcfbf156564e73a3935d7e750891fab2abf302e735104fd4050a7e1 + + - name: krita + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_TESTING=OFF + build-options: + env: + PYTHONPATH: /app/lib/python3/dist-packages + post-install: + - rm -r /app/share/icons/hicolor/{1024x1024,scalable} + sources: + - type: git + url: https://anongit.kde.org/krita diff --git a/packaging/linux/flatpak/org.kde.krita-stable.yaml b/packaging/linux/flatpak/org.kde.krita-stable.yaml new file mode 100644 index 0000000000..2e6e4bad15 --- /dev/null +++ b/packaging/linux/flatpak/org.kde.krita-stable.yaml @@ -0,0 +1,279 @@ +app-id: org.kde.krita-nightly +default-branch: stable +runtime: org.kde.Platform +runtime-version: '5.12' +sdk: org.kde.Sdk +command: krita +rename-icon: calligrakrita +finish-args: + - --share=ipc + - --socket=x11 + - --share=network + - --device=dri + - --socket=pulseaudio + - --filesystem=host + - --filesystem=xdg-config/kdeglobals:ro + - --env=PYTHONPATH=/app/lib/python3/dist-packages + - --env=TMPDIR=/var/tmp +cleanup: + - /include + - /lib/pkgconfig + - /lib/cmake + - /share/aclocal + - /share/pkgconfig + - /share/info + - /share/man + - /cmake + - '*.a' + - '*.la' + - '*.cmake' +modules: + - name: sip + buildsystem: simple + build-commands: + - python3 configure.py + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --incdir=/app/include/python3 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages + --sip-module=PyQt5.sip + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/sip/4.19.17/sip-4.19.17.tar.gz + sha256: 12bcd8f4d5feefc105bc075d12c5090ee783f7380728563c91b8b95d0ec45df3 + + - name: pyqt + buildsystem: simple + build-commands: + - python3 configure.py + --confirm-license + --sip-incdir=/app/include/python3 + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --designer-plugindir=/app/lib/plugins/designer + --qml-plugindir=/app/lib/plugins/PyQt5 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages/PyQt5 + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.2/PyQt5_gpl-5.12.2.tar.gz + sha256: c565829e77dc9c281aa1a0cdf2eddaead4e0f844cbaf7a4408441967f03f5f0f + + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=/app --with-libraries=system + - ./b2 -j $FLATPAK_BUILDER_N_JOBS install + sources: + - type: archive + url: https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2 + sha256: 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406 + + - name: eigen + buildsystem: cmake-ninja + builddir: true + cleanup: + - '*' + sources: + - type: archive + url: https://bitbucket.org/eigen/eigen/get/3.3.7.tar.bz2 + sha256: 9f13cf90dedbe3e52a19f43000d71fdf72e986beb9a5436dddcd61ff9d77a3ce + + - name: quazip + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://github.com/stachenov/quazip/archive/0.7.6.tar.gz + sha256: 4118a830a375a81211956611cc34b1b5b4ddc108c126287b91b40c2493046b70 + - type: shell + commands: + - sed -i 's|${CMAKE_ROOT}/Modules|share/cmake|' CMakeLists.txt + + - name: exiv2 + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - /bin + sources: + - type: archive + url: https://www.exiv2.org/builds/exiv2-0.27.1-Source.tar.gz + sha256: f125286980fd1bcb28e188c02a93946951c61e10784720be2301b661a65b3081 + + - name: ilmbase + config-opts: + - --disable-static + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz + sha256: 456978d1a978a5f823c7c675f3f36b0ae14dba36638aeaa3c4b0e784f12a3862 + + - name: openexr + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz + sha256: fd6cb3a87f8c1a233be17b94c74799e6241d50fc5efd4df75c7a4b9cf4e25ea6 + + - name: libraw + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://www.libraw.org/data/LibRaw-0.19.2.tar.gz + sha256: 400d47969292291d297873a06fb0535ccce70728117463927ddd9452aa849644 + + - name: opencolorio + buildsystem: cmake # ninja build broken (fixed in 2.0) + builddir: true + build-options: + arch: + arm: + config-opts: + - -DOCIO_USE_SSE=OFF + aarch64: + config-opts: + - -DOCIO_USE_SSE=OFF + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DOCIO_BUILD_STATIC=OFF + - -DCMAKE_CXX_FLAGS='-Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-error=cast-function-type' + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/imageworks/OpenColorIO/archive/v1.1.1.tar.gz + sha256: c9b5b9def907e1dafb29e37336b702fff22cc6306d445a13b1621b8a754c14c8 + + - name: vc + skip-arches: + - aarch64 + - arm + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz + sha256: 08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe + - type: shell + commands: + - sed -i 's/x86|/x86|i686|/' CMakeLists.txt + + - name: poppler-data + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-data-0.4.9.tar.gz + sha256: 1f9c7e7de9ecd0db6ab287349e31bf815ca108a5a175cf906a90163bdbe32012 + + - name: poppler + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_GTK_TESTS=OFF + - -DBUILD_QT5_TESTS=OFF + - -DBUILD_CPP_TESTS=OFF + - -DENABLE_UTILS=OFF + - -DENABLE_CPP=OFF + - -DENABLE_GLIB=OFF + - -DENABLE_LIBOPENJPEG=none + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-0.77.0.tar.xz + sha256: 7267eb4cbccd64a58244b8211603c1c1b6bf32c7f6a4ced2642865346102f36b + + - name: gsl + config-opts: + - --disable-static + cleanup: + - /bin + sources: + - type: archive + url: https://ftpmirror.gnu.org/gnu/gsl/gsl-2.5.tar.gz + sha256: 0460ad7c2542caaddc6729762952d345374784100223995eb14d614861f2258d + + - name: gmic-qt + buildsystem: cmake-ninja + builddir: true + subdir: gmic-qt + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DGMIC_QT_HOST=krita + - -DGMIC_PATH=../src + - -DCMAKE_CXX_FLAGS=-lfftw3_threads + sources: + - type: archive + url: https://gmic.eu/files/source/gmic_2.6.4.tar.gz + sha256: 4cd88b2dca6b9b1a330ab4556d36656bafb98e4e9814bf0448545b27ef18dae3 + + - name: x264 + config-opts: + - --disable-cli + - --enable-shared + sources: + - type: archive + url: https://download.videolan.org/x264/snapshots/x264-snapshot-20190305-2245-stable.tar.bz2 + sha256: be52c96ef8bd930fbc1ecff03abac9b94976b444ea7641345e08e20d9e594d16 + + - name: ffmpeg + config-opts: + - --enable-rpath + - --enable-gpl + - --disable-static + - --enable-shared + - --disable-doc + - --disable-ffplay + - --disable-ffprobe + - --enable-libopus + - --enable-libvpx + - --enable-libx264 + cleanup: + - /share/ffmpeg/examples + sources: + - type: archive + url: https://www.ffmpeg.org/releases/ffmpeg-4.1.4.tar.xz + sha256: f1f049a82fcfbf156564e73a3935d7e750891fab2abf302e735104fd4050a7e1 + + - name: krita + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_TESTING=OFF + build-options: + env: + PYTHONPATH: /app/lib/python3/dist-packages + post-install: + - rm -r /app/share/icons/hicolor/{1024x1024,scalable} + sources: + - type: git + url: https://anongit.kde.org/krita + branch: krita/4.2 diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml index b80c3a1d40..c3fefa858f 100644 --- a/packaging/linux/snap/snapcraft.yaml +++ b/packaging/linux/snap/snapcraft.yaml @@ -1,161 +1,162 @@ name: krita -version: 4.2.1 +version: 4.2.2 summary: Krita is the digital painting studio for artists description: Krita is a creative application for raster images. Whether you want to create from scratch or work with existing images, Krita is for you. You can work with photos or scanned images, or start with a blank slate. Krita supports most graphics tablets out of the box. base: core18 apps: krita: command: qt5-launch usr/bin/krita plugs: [x11, unity7, home, opengl, network, network-bind, removable-media, desktop, desktop-legacy] parts: krita: plugin: cmake configflags: - "-DCMAKE_INSTALL_PREFIX=/usr" - "-DCMAKE_BUILD_TYPE=Release" - "-DENABLE_TESTING=OFF" - "-DBUILD_TESTING=OFF" + - "-DHIDE_SAFE_ASSERTS=OFF" - "-DKDE_SKIP_TEST_SETTINGS=ON" - source: https://download.kde.org/stable/krita/4.2.1/krita-4.2.1.tar.xz + source: https://download.kde.org/stable/krita/4.2.2/krita-4.2.2.tar.xz # Use these instead to build from the git source # source: https://anongit.kde.org/krita.git # source-type: git # source-branch: master build-packages: - gettext - build-essential - cmake - libboost-dev - libboost-system-dev - libeigen3-dev - libexiv2-dev - libfftw3-dev - libfontconfig1-dev - libfreetype6-dev - libgl1-mesa-dev - libglew-dev - libglib2.0-dev - libglu1-mesa-dev - libgsf-1-dev - libgsl-dev - libjpeg-dev - liblcms2-dev - libopenexr-dev - libpng-dev - libpoppler-qt5-dev - libtiff5-dev - libvc-dev - libopencolorio-dev - libx11-dev - libxml2-dev - libxslt1-dev - libxi-dev - pkg-config - vc-dev - zlib1g-dev - libkf5kdcraw-dev - shared-mime-info - libopenimageio-dev - extra-cmake-modules - libkf5archive-dev - libkf5coreaddons-dev - libkf5guiaddons-dev - libkf5itemmodels-dev - libkf5itemviews-dev - libkf5widgetsaddons-dev - libkf5i18n-dev - libkf5windowsystem-dev - libkf5completion-dev - libkf5iconthemes-dev - libkf5kiocore5 - libqt5svg5-dev - libqt5x11extras5-dev - libqt5opengl5-dev - libquazip5-dev - qtmultimedia5-dev - qtdeclarative5-dev - libkf5crash-dev runtime: plugin: nil stage-packages: - libexiv2-26 - libfftw3-double3 - libgomp1 - libgsl23 - libilmbase12 - libjpeg8 - liblcms2-2 - libopencolorio1v5 - libopenexr22 - libpng16-16 - libstdc++6 - libtiff5 - libx11-6 - libxcb1 - libxi6 - zlib1g - libpoppler-qt5-1 - shared-mime-info - libboost-system1.65.1 - librtmp1 - libqt5quickwidgets5 - libkf5archive5 - libkf5completion5 - libkf5configcore5 - libkf5configgui5 - libkf5coreaddons5 - libkf5guiaddons5 - libkf5i18n5 - libkf5itemviews5 - libkf5widgetsaddons5 - libkf5windowsystem5 - libkf5crash5 - libqt5concurrent5 - libqt5core5a - libqt5dbus5 - libqt5gui5 - libqt5network5 - libqt5printsupport5 - libqt5svg5 - libqt5widgets5 - libqt5x11extras5 - libqt5xml5 - locales - libc-bin - libquazip5-1 - libqt5quick5 - libqt5quickparticles5 - libqt5quickshapes5 - qt5-qmltooling-plugins - libqt5qml5 - libqt5multimedia5 - libqt5multimediagsttools5 - libqt5multimediaquick5 - libqt5multimediawidgets5 prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" plasma-integration: plugin: nil stage-packages: - plasma-integration - kde-style-breeze - breeze-icon-theme - kio # runtime slaves for kio prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" launcher: plugin: dump source: . organize: qt5-launch: bin/qt5-launch kf5-locale-gen: bin/kf5-locale-gen diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh index edcb87ce6e..bcb33201b5 100755 --- a/packaging/macos/osxbuild.sh +++ b/packaging/macos/osxbuild.sh @@ -1,536 +1,536 @@ #!/usr/bin/env bash # osxbuild.sh automates building and installing of krita and krita dependencies # for OSX, the script only needs you to set BUILDROOT environment to work # properly. # # Run with no args for a short help about each command. # builddeps: Attempts to build krita dependencies in the necessary order, # intermediate steps for creating symlinks and fixing rpath of some # packages midway is also managed. Order goes from top to bottom, to add # new steps just place them in the proper place. # rebuilddeps: This re-runs all make and make install of dependencies 3rdparty # this was needed as deleting the entire install directory an rerunning build # step for dependencies does not install if they are already built. This step # forces installation. Have not tested it lately so it might not be needed anymore # build: Runs cmake build and make step for krita sources. It always run cmake step, so # it might take a bit longer than a pure on the source tree. The script tries # to set the make flag -jN to a proper N. # install: Runs install step for krita sources. # fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by # default it fails to set a proper @rpath # buildinstall: Runs build, install and fixboost steps.# if test -z $BUILDROOT; then echo "ERROR: BUILDROOT env not set, exiting!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" exit fi echo "BUILDROOT set to ${BUILDROOT}" export KIS_SRC_DIR=${BUILDROOT}/krita export KIS_TBUILD_DIR=${BUILDROOT}/depbuild export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall export KIS_DOWN_DIR=${BUILDROOT}/down export KIS_BUILD_DIR=${BUILDROOT}/kisbuild export KIS_INSTALL_DIR=${BUILDROOT}/i # flags for OSX environment # Qt only supports from 10.12 up, and https://doc.qt.io/qt-5/macos.html#target-platforms warns against setting it lower export MACOSX_DEPLOYMENT_TARGET=10.12 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.12 # Build time variables if test -z $(which cmake); then echo "ERROR: cmake not found, exiting!" exit fi export PATH=${KIS_INSTALL_DIR}/bin:$PATH export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH} export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH} export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH} # export CPPFLAGS=-I${KIS_INSTALL_DIR}/include # export LDFLAGS=-L${KIS_INSTALL_DIR}/lib export FRAMEWORK_PATH=${KIS_INSTALL_DIR}/lib/ # export PYTHONHOME=${KIS_INSTALL_DIR} # export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5 # This will make the debug output prettier export KDE_COLOR_DEBUG=1 export QTEST_COLORED=1 export OUPUT_LOG="${BUILDROOT}/osxbuild.log" export ERROR_LOG="${BUILDROOT}/osxbuild-error.log" printf "" > "${OUPUT_LOG}" printf "" > "${ERROR_LOG}" # configure max core for make compile ((MAKE_THREADS=1)) if test ${OSTYPE} == "darwin*"; then ((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1)) fi # Prints stderr and stdout to log files # >(tee) works but breaks sigint log_cmd () { if [[ "${VERBOSE}" ]]; then "$@" 1>> ${OUPUT_LOG} 2>> ${ERROR_LOG} else "$@" 2>> ${ERROR_LOG} | tee -a ${OUPUT_LOG} > /dev/null fi } # Log messages to logfile log () { if [[ "${VERBOSE}" ]]; then printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} else printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} > /dev/null fi } # if previous command gives error # print msg print_if_error() { error_stat="${?}" if [ ${error_stat} -ne 0 ]; then printf "\e[31m%s %s\e[0m\n" "Error:" "${1}" 2>> ${ERROR_LOG} printf "%s\r" "${error_stat}" fi } # print status messges print_msg() { printf "\e[32m%s\e[0m\n" "${1}" printf "%s\n" "${1}" >> ${OUPUT_LOG} } check_dir_path () { printf "%s" "Checking if ${1} exists and is dir... " if test -d ${1}; then echo -e "OK" elif test -e ${1}; then echo -e "\n\tERROR: file ${1} exists but is not a directory!" >&2 return 1 else echo -e "Creating ${1}" mkdir ${1} fi return 0 } # builds dependencies for the first time cmake_3rdparty () { cd ${KIS_TBUILD_DIR} local build_pkgs=("${@}") # convert to array if [[ ${2} = "1" ]]; then local nofix="true" local build_pkgs=(${build_pkgs[@]:0:1}) fi for package in ${build_pkgs[@]} ; do print_msg "Building ${package}" log_cmd cmake --build . --config RelWithDebInfo --target ${package} if [[ ! $(print_if_error "Failed build ${package}") ]]; then print_msg "Build Success! ${package}" fi # fixes does not depend on failure if [[ ! ${nofix} ]]; then build_3rdparty_fixes ${package} fi done } build_3rdparty_fixes(){ pkg=${1} if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5" elif [[ "${pkg}" = "ext_openexr" ]]; then # open exr will fail the first time is called # rpath needs to be fixed an build rerun log "Fixing rpath on openexr file: b44ExpLogTable" log "Fixing rpath on openexr file: dwaLookups" log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups # we must rerun build! cmake_3rdparty ext_openexr "1" elif [[ "${pkg}" = "ext_fontconfig" ]]; then log "fixing rpath on fc-cache" log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_fontconfig/ext_fontconfig-prefix/src/ext_fontconfig-build/fc-cache/.libs/fc-cache # rerun rebuild cmake_3rdparty ext_fontconfig "1" fi } build_3rdparty () { print_msg "building in ${KIS_TBUILD_DIR}" log "$(check_dir_path ${KIS_TBUILD_DIR})" log "$(check_dir_path ${KIS_DOWN_DIR})" log "$(check_dir_path ${KIS_INSTALL_DIR})" cd ${KIS_TBUILD_DIR} log_cmd cmake ${KIS_SRC_DIR}/3rdparty/ \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \ -DINSTALL_ROOT=${KIS_INSTALL_DIR} # -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \ # -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib print_msg "finished 3rdparty build setup" if [[ -n ${1} ]]; then cmake_3rdparty "${@}" exit fi # build 3rdparty tools # The order must not be changed! cmake_3rdparty \ ext_pkgconfig \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_lcms2 \ ext_ocio \ ext_openexr cmake_3rdparty \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_freetype \ ext_fontconfig \ ext_poppler # Stop if qmake link was not created # this meant qt build fail and further builds will # also fail. log_cmd test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5" if [[ $(print_if_error "qmake link missing!") ]]; then printf " link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing! It probably means ext_qt failed!! check, fix and rerun!\n" exit fi # for python cmake_3rdparty \ ext_python \ ext_sip \ ext_pyqt cmake_3rdparty ext_libheif cmake_3rdparty \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } # Recall cmake for all 3rd party packages # make is only on target first run # subsequent runs only call make install rebuild_3rdparty () { print_msg "starting rebuild of 3rdparty packages" build_install_ext() { for pkg in ${@:1:${#@}}; do { cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp } log "Installing ${pkg} files..." rm ${pkg}-configure ${pkg}-build ${pkg}-install cmake_3rdparty ${pkg} cd ${KIS_TBUILD_DIR} done } # Do not process complete list only pkgs given. if ! test -z ${1}; then build_install_ext ${@} exit fi build_install_ext \ ext_pkgconfig \ ext_iconv \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_expat \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_patch \ ext_lcms2 \ ext_ocio \ ext_ilmbase \ ext_openexr \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_fontconfig \ ext_freetype \ ext_poppler \ ext_python \ ext_sip \ ext_pyqt \ build_install_ext \ ext_yasm \ ext_nasm \ ext_libx265 \ ext_libde265 \ ext_libheif \ # Build kde_frameworks build_install_ext \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } #not tested set_krita_dirs() { if [[ -n ${1} ]]; then KIS_BUILD_DIR=${BUILDROOT}/b_${1} KIS_INSTALL_DIR=${BUILDROOT}/i_${1} KIS_SRC_DIR=${BUILDROOT}/src_${1} fi } # build_krita # run cmake krita build_krita () { export DYLD_FRAMEWORK_PATH=${FRAMEWORK_PATH} echo ${KIS_BUILD_DIR} echo ${KIS_INSTALL_DIR} log_cmd check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} cmake ${KIS_SRC_DIR} \ -DFOUNDATION_BUILD=ON \ -DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DDEFINE_NO_DEPRECATED=1 \ -DBUILD_TESTING=OFF \ - -DHIDE_SAFE_ASSERTS=ON \ + -DHIDE_SAFE_ASSERTS=OFF \ -DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \ -DPYQT_SIP_DIR_OVERRIDE=${KIS_INSTALL_DIR}/share/sip/ \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \ -DPYTHON_INCLUDE_DIR=${KIS_INSTALL_DIR}/lib/Python.framework/Headers # copiling phase make -j${MAKE_THREADS} # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make -j${MAKE_THREADS} fi } build_krita_tarball () { filename="$(basename ${1})" KIS_CUSTOM_BUILD="${BUILDROOT}/releases/${filename%.tar.gz}" print_msg "Tarball BUILDROOT is ${KIS_CUSTOM_BUILD}" filename_dir=$(dirname "${1}") cd "${filename_dir}" file_abspath="$(pwd)/${1##*/}" mkdir "${KIS_CUSTOM_BUILD}" 2> /dev/null cd "${KIS_CUSTOM_BUILD}" mkdir "src" "build" 2> /dev/null tar -xzf "${file_abspath}" --strip-components=1 --directory "src" if [[ $(print_if_error "Untar ${file_abspath} failed!") ]]; then exit fi KIS_BUILD_DIR="${KIS_CUSTOM_BUILD}/build" KIS_SRC_DIR="${KIS_CUSTOM_BUILD}/src" build_krita print_msg "Build done!" print_msg "to install run osxbuild.sh install ${KIS_BUILD_DIR}" } install_krita () { # custom install provided if [[ -n "${1}" ]]; then KIS_BUILD_DIR="${1}" fi print_msg "Install krita from ${KIS_BUILD_DIR}" log_cmd check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} if [[ $(print_if_error "could not cd to ${KIS_BUILD_DIR}") ]]; then exit fi make install # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make install fi } # Runs all fixes for path and packages. # Historically only fixed boost @rpath fix_boost_rpath () { # helpers to define function only once fixboost_find () { for FILE in "${@}"; do if [[ -n "$(otool -L $FILE | grep boost)" ]]; then log "Fixing -- $FILE" log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE fi done } batch_fixboost() { xargs -P4 -I FILE bash -c 'fixboost_find "FILE"' } export -f fixboost_find export -f log export -f log_cmd print_msg "Fixing boost in... ${KIS_INSTALL_DIR}" # install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita # echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin" # install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita find -L "${KIS_INSTALL_DIR}" -name '*so' -o -name '*dylib' | batch_fixboost log "Fixing boost done!" } print_usage () { printf "USAGE: osxbuild.sh [pkg|file]\n" printf "BUILDSTEPS:\t\t" printf "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg" printf "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg \t\t\t usefull for cleaning install directory and quickly reinstall all deps." printf "\n fixboost \t\t Fixes broken boost \@rpath on OSX" printf "\n build \t\t\t Builds krita" printf "\n buildtarball \t\t\t Builds krita from provided [file] tarball" printf "\n install \t\t Installs krita. Optionally accepts a [build dir] as argument \t\t\t this will install krita from given directory" printf "\n buildinstall \t\t Build and Installs krita, running fixboost after installing" printf "\n" } if [[ ${#} -eq 0 ]]; then echo "ERROR: No option given!" print_usage exit 1 fi if [[ ${1} = "builddeps" ]]; then build_3rdparty "${@:2}" elif [[ ${1} = "rebuilddeps" ]]; then rebuild_3rdparty "${@:2}" elif [[ ${1} = "fixboost" ]]; then if [[ -d ${1} ]]; then KIS_BUILD_DIR="${1}" fi fix_boost_rpath elif [[ ${1} = "build" ]]; then build_krita ${2} elif [[ ${1} = "buildtarball" ]]; then # uncomment line to optionally change # install directory providing a third argument # This is not on by default as build success requires all # deps installed in the given dir beforehand. # KIS_INSTALL_DIR=${3} build_krita_tarball ${2} elif [[ ${1} = "install" ]]; then install_krita ${2} fix_boost_rpath elif [[ ${1} = "buildinstall" ]]; then build_krita ${2} install_krita ${2} fix_boost_rpath ${2} elif [[ ${1} = "test" ]]; then ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita else echo "Option ${1} not supported" print_usage exit 1 fi # after finishig sometimes it complains about missing matching quotes. diff --git a/plugins/assistants/Assistants/VanishingPointAssistant.cc b/plugins/assistants/Assistants/VanishingPointAssistant.cc index d3af88d14c..abc93b542e 100644 --- a/plugins/assistants/Assistants/VanishingPointAssistant.cc +++ b/plugins/assistants/Assistants/VanishingPointAssistant.cc @@ -1,318 +1,318 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2014 Wolthera van Hövell tot Westerflier * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "VanishingPointAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include VanishingPointAssistant::VanishingPointAssistant() : KisPaintingAssistant("vanishing point", i18n("Vanishing Point assistant")) { } VanishingPointAssistant::VanishingPointAssistant(const VanishingPointAssistant &rhs, QMap &handleMap) : KisPaintingAssistant(rhs, handleMap) , m_canvas(rhs.m_canvas) , m_referenceLineDensity(rhs.m_referenceLineDensity) { } KisPaintingAssistantSP VanishingPointAssistant::clone(QMap &handleMap) const { return KisPaintingAssistantSP(new VanishingPointAssistant(*this, handleMap)); } QPointF VanishingPointAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { //Q_ASSERT(handles().size() == 1 || handles().size() == 5); //code nicked from the perspective ruler. qreal dx = pt.x() - strokeBegin.x(); qreal dy = pt.y() - strokeBegin.y(); if (dx * dx + dy * dy < 4.0) { // allow some movement before snapping return strokeBegin; } //dbgKrita<canvasWidget()->mapFromGlobal(QCursor::pos()); m_canvas = canvas; } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<paintingAssistantsDecoration()->isEditingAssistants() == false && isAssistantComplete()) { if (isSnappingActive() && previewVisible == true) { //don't draw if invalid. QTransform initialTransform = converter->documentToWidgetTransform(); QPointF startPoint = initialTransform.map(*handles()[0]); QLineF snapLine= QLineF(startPoint, mousePos); QRect viewport= gc.viewport(); KisAlgebra2D::intersectLineRect(snapLine, viewport); QRect bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; if (bounds.contains(startPoint.toPoint())){ path.moveTo(startPoint); path.lineTo(snapLine.p1()); } else { path.moveTo(snapLine.p1()); path.lineTo(snapLine.p2()); } drawPreview(gc, path);//and we draw the preview. } } // editor specific controls display if (canvas->paintingAssistantsDecoration()->isEditingAssistants()) { // draws a circle around the vanishing point node while editing QTransform initialTransform = converter->documentToWidgetTransform(); QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point QPointF p1 = initialTransform.map(*sideHandles()[0]); QPointF p2 = initialTransform.map(*sideHandles()[1]); QPointF p3 = initialTransform.map(*sideHandles()[2]); QPointF p4 = initialTransform.map(*sideHandles()[3]); QRectF ellipse = QRectF(QPointF(p0.x() -15, p0.y() -15), QSizeF(30, 30)); QPainterPath pathCenter; pathCenter.addEllipse(ellipse); drawPath(gc, pathCenter, isSnappingActive()); QColor paintingColor = effectiveAssistantColor(); // draw the lines connecting the different nodes QPen penStyle(paintingColor, 2.0, Qt::SolidLine); if (!isSnappingActive()) { QColor snappingColor = paintingColor; snappingColor.setAlpha(snappingColor.alpha() * 0.2); penStyle.setColor(snappingColor); } gc.save(); gc.setPen(penStyle); gc.drawLine(p0, p1); gc.drawLine(p0, p3); gc.drawLine(p1, p2); gc.drawLine(p3, p4); gc.restore(); } // draw references guide for vanishing points at specified density // this is shown as part of the preview, so don't show if preview is off - if( canvas->paintingAssistantsDecoration()->outlineVisibility() && this->isSnappingActive() ) { + if ( (assistantVisible && canvas->paintingAssistantsDecoration()->outlineVisibility()) && this->isSnappingActive() ) { // cycle through degrees from 0 to 180. We are doing an infinite line, so we don't need to go 360 QTransform initialTransform = converter->documentToWidgetTransform(); QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point for (int currentAngle=0; currentAngle <= 180; currentAngle = currentAngle + m_referenceLineDensity ) { // determine the correct angle based on the iteration float xPos = cos(currentAngle * M_PI / 180); float yPos = sin(currentAngle * M_PI / 180); QPointF unitAngle; unitAngle.setX(p0.x() + xPos); unitAngle.setY(p0.y() + yPos); // find point QLineF snapLine= QLineF(p0, unitAngle); QRect viewport= gc.viewport(); KisAlgebra2D::intersectLineRect(snapLine, viewport); // make a line from VP center to edge of canvas with that angle QPainterPath path; path.moveTo(snapLine.p1()); path.lineTo(snapLine.p2()); drawPreview(gc, path);//and we draw the preview. } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); } void VanishingPointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { if (!m_canvas || !isAssistantComplete()) { return; } if (assistantVisible == false || m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) { return; } QTransform initialTransform = converter->documentToWidgetTransform(); QPointF p0 = initialTransform.map(*handles()[0]); // draws an "X" QPainterPath path; path.moveTo(QPointF(p0.x() - 10.0, p0.y() - 10.0)); path.lineTo(QPointF(p0.x() + 10.0, p0.y() + 10.0)); path.moveTo(QPointF(p0.x() - 10.0, p0.y() + 10.0)); path.lineTo(QPointF(p0.x() + 10.0, p0.y() - 10.0)); drawPath(gc, path, isSnappingActive()); } QPointF VanishingPointAssistant::buttonPosition() const { return (*handles()[0]); } void VanishingPointAssistant::setReferenceLineDensity(float value) { // cannot have less than 1 degree value if (value < 1.0) { value = 1.0; } m_referenceLineDensity = value; } float VanishingPointAssistant::referenceLineDensity() { return m_referenceLineDensity; } bool VanishingPointAssistant::isAssistantComplete() const { return handles().size() > 0; // only need one point to be ready } void VanishingPointAssistant::saveCustomXml(QXmlStreamWriter* xml) { xml->writeStartElement("angleDensity"); xml->writeAttribute("value", KisDomUtils::toString( this->referenceLineDensity())); xml->writeEndElement(); } bool VanishingPointAssistant::loadCustomXml(QXmlStreamReader* xml) { if (xml && xml->name() == "angleDensity") { this->setReferenceLineDensity((float)KisDomUtils::toDouble(xml->attributes().value("value").toString())); } return true; } VanishingPointAssistantFactory::VanishingPointAssistantFactory() { } VanishingPointAssistantFactory::~VanishingPointAssistantFactory() { } QString VanishingPointAssistantFactory::id() const { return "vanishing point"; } QString VanishingPointAssistantFactory::name() const { return i18n("Vanishing Point"); } KisPaintingAssistant* VanishingPointAssistantFactory::createPaintingAssistant() const { return new VanishingPointAssistant; } diff --git a/plugins/color/lcms2engine/tests/TestLcmsRGBP2020PQColorSpace.cpp b/plugins/color/lcms2engine/tests/TestLcmsRGBP2020PQColorSpace.cpp index e4cf7a65ec..fd1f8aaf1c 100644 --- a/plugins/color/lcms2engine/tests/TestLcmsRGBP2020PQColorSpace.cpp +++ b/plugins/color/lcms2engine/tests/TestLcmsRGBP2020PQColorSpace.cpp @@ -1,185 +1,186 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestLcmsRGBP2020PQColorSpace.h" #include #include "sdk/tests/kistest.h" #include "kis_debug.h" #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorModelStandardIds.h" inline QString truncated(QString value) { value.truncate(24); return value; } enum SourceType { SDR, HDR, HDR_PQ }; void testRoundTrip(const KoColorSpace *srcCS, const KoColorSpace *dstCS, SourceType sourceIsPQ) { - qDebug() << "Testing:" << srcCS->id() << truncated(srcCS->profile()->name()) - << "->" - << dstCS->id() << truncated(dstCS->profile()->name()); + /* + * On some systems these colorspaces cannot be created, so don't die: + */ + if (!srcCS | !dstCS) return; KoColor srcColor(srcCS); KoColor dstColor(dstCS); QVector refChannels; if (sourceIsPQ == HDR) { refChannels << 2.8; // R refChannels << 1.8; // G refChannels << 0.8; // B refChannels << 0.9; // A } else if (sourceIsPQ == HDR_PQ) { refChannels << 0.9; // R (PQ) refChannels << 0.7; // G (PQ) refChannels << 0.1; // B (PQ) refChannels << 0.9; // A } else if (sourceIsPQ == SDR) { refChannels << 0.15; // R refChannels << 0.17; // G refChannels << 0.19; // B refChannels << 0.90; // A } srcCS->fromNormalisedChannelsValue(srcColor.data(), refChannels); srcCS->convertPixelsTo(srcColor.data(), dstColor.data(), dstCS, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); dstCS->convertPixelsTo(dstColor.data(), srcColor.data(), srcCS, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QVector result(4); srcCS->normalisedChannelsValue(srcColor.data(), result); QList channels = srcCS->channels(); // 5% tolerance for CMYK, 4% for 8-bit, and 1% for everything else const float tolerance = dstCS->colorModelId() == CMYKAColorModelID ? 0.05 : (dstCS->colorDepthId() == Integer8BitsColorDepthID || srcCS->colorDepthId() == Integer8BitsColorDepthID) ? 0.04 : 0.01; bool roundTripIsCorrect = true; for (int i = 0; i < 4; i++) { roundTripIsCorrect &= qAbs(refChannels[i] - result[i]) < tolerance; } if (!roundTripIsCorrect) { for (int i = 0; i < 4; i++) { qDebug() << channels[i]->name() << "ref" << refChannels[i] << "result" << result[i]; } } QVERIFY(roundTripIsCorrect); } void testRoundTrip(const KoID &linearColorDepth, const KoID &pqColorDepth, SourceType sourceIsPQ) { const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile(); const KoColorProfile *p2020G10Profile = KoColorSpaceRegistry::instance()->p2020G10Profile(); const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), linearColorDepth.id(), p2020G10Profile); const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), pqColorDepth.id(), p2020PQProfile);; if (sourceIsPQ == HDR_PQ) { std::swap(srcCS, dstCS); } testRoundTrip(srcCS, dstCS, sourceIsPQ); } void TestLcmsRGBP2020PQColorSpace::test() { const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile(); const KoColorProfile *p2020G10Profile = KoColorSpaceRegistry::instance()->p2020G10Profile(); const KoColorProfile *p709G10Profile = KoColorSpaceRegistry::instance()->p709G10Profile(); QVERIFY(p2020PQProfile); QVERIFY(p2020G10Profile); QVERIFY(p709G10Profile); QVector linearModes; linearModes << Float16BitsColorDepthID; linearModes << Float32BitsColorDepthID; QVector pqModes; pqModes << Integer8BitsColorDepthID; pqModes << Integer16BitsColorDepthID; pqModes << Float16BitsColorDepthID; pqModes << Float32BitsColorDepthID; Q_FOREACH(const KoID &src, linearModes) { Q_FOREACH(const KoID &dst, pqModes) { testRoundTrip(src, dst, HDR); } } Q_FOREACH(const KoID &src, linearModes) { Q_FOREACH(const KoID &dst, pqModes) { testRoundTrip(src, dst, HDR_PQ); } } } void TestLcmsRGBP2020PQColorSpace::testInternalConversions() { const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile(); QVector pqModes; pqModes << Integer16BitsColorDepthID; pqModes << Float16BitsColorDepthID; pqModes << Float32BitsColorDepthID; Q_FOREACH(const KoID &src, pqModes) { Q_FOREACH(const KoID &dst, pqModes) { if (src == dst) continue; const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), src.id(), p2020PQProfile); const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), dst.id(), p2020PQProfile); testRoundTrip(srcCS, dstCS, HDR_PQ); } } } void TestLcmsRGBP2020PQColorSpace::testConvertToCmyk() { const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile(); const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), p2020PQProfile); const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), 0); testRoundTrip(srcCS, dstCS, SDR); } KISTEST_MAIN(TestLcmsRGBP2020PQColorSpace) diff --git a/plugins/dockers/animation/tests/timeline_model_test.cpp b/plugins/dockers/animation/tests/timeline_model_test.cpp index 1b331225bc..eb44be5abc 100644 --- a/plugins/dockers/animation/tests/timeline_model_test.cpp +++ b/plugins/dockers/animation/tests/timeline_model_test.cpp @@ -1,312 +1,314 @@ /* * 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 "timeline_model_test.h" #include "kis_image.h" #include "kis_node.h" #include "kis_paint_device.h" #include #include #include #include #include "kis_image_animation_interface.h" #include "KisDocument.h" #include "KisPart.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" #include "kis_undo_adapter.h" #include "timeline_frames_view.h" #include "timeline_frames_model.h" #include "kis_node_dummies_graph.h" #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" #include #include void TimelineModelTest::init() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); m_shapeController = new KisShapeController(m_doc, m_nameServer); //m_nodeModel = new KisNodeModel(0); initBase(); + + m_doc->setCurrentImage(m_image); } void TimelineModelTest::cleanup() { cleanupBase(); //delete m_nodeModel; delete m_shapeController; delete m_nameServer; delete m_doc; } #include "timeline_frames_index_converter.h" void TimelineModelTest::testConverter() { constructImage(); addSelectionMasks(); m_shapeController->setImage(m_image); m_layer1->enableAnimation(); m_layer1->setUseInTimeline(true); m_layer2->setUseInTimeline(true); m_sel3->setUseInTimeline(true); TimelineFramesIndexConverter converter(m_shapeController); QCOMPARE(converter.rowCount(), 3); QCOMPARE(converter.rowForDummy(m_shapeController->dummyForNode(m_layer1)), 2); QCOMPARE(converter.rowForDummy(m_shapeController->dummyForNode(m_layer2)), 1); QCOMPARE(converter.rowForDummy(m_shapeController->dummyForNode(m_sel3)), 0); QCOMPARE(converter.dummyFromRow(2), m_shapeController->dummyForNode(m_layer1)); QCOMPARE(converter.dummyFromRow(1), m_shapeController->dummyForNode(m_layer2)); QCOMPARE(converter.dummyFromRow(0), m_shapeController->dummyForNode(m_sel3)); TimelineNodeListKeeper keeper(0, m_shapeController); QCOMPARE(keeper.rowCount(), 3); QCOMPARE(keeper.rowForDummy(m_shapeController->dummyForNode(m_layer1)), 2); QCOMPARE(keeper.rowForDummy(m_shapeController->dummyForNode(m_layer2)), 1); QCOMPARE(keeper.rowForDummy(m_shapeController->dummyForNode(m_sel3)), 0); QCOMPARE(keeper.dummyFromRow(2), m_shapeController->dummyForNode(m_layer1)); QCOMPARE(keeper.dummyFromRow(1), m_shapeController->dummyForNode(m_layer2)); QCOMPARE(keeper.dummyFromRow(0), m_shapeController->dummyForNode(m_sel3)); TimelineNodeListKeeper::OtherLayersList list = keeper.otherLayersList(); Q_FOREACH (const TimelineNodeListKeeper::OtherLayer &l, list) { qDebug() << ppVar(l.name) << ppVar(l.dummy->node()->name()); } } void TimelineModelTest::testModel() { QScopedPointer model(new TimelineFramesModel(0)); } struct TestingInterface : TimelineFramesModel::NodeManipulationInterface { TestingInterface(KisImageSP image) : m_image(image) {} KisLayerSP addPaintLayer() const override { KisNodeSP parent = m_image->root(); KisNodeSP after = parent->lastChild(); KisPaintLayerSP layer = new KisPaintLayer(const_cast(m_image.data()), m_image->nextLayerName(), OPACITY_OPAQUE_U8, m_image->colorSpace()); m_image->undoAdapter()->addCommand( new KisImageLayerAddCommand(m_image, layer, parent, after, false, false)); return layer; } void removeNode(KisNodeSP node) const override { m_image->undoAdapter()->addCommand( new KisImageLayerRemoveCommand(m_image, node)); } bool setNodeProperties(KisNodeSP, KisImageSP, KisBaseNode::PropertyList) const override { return false; } private: KisImageSP m_image; }; void TimelineModelTest::testView() { #ifndef ENABLE_GUI_TESTS return; #endif QDialog dlg; QFont font; font.setPointSizeF(9); dlg.setFont(font); QSpinBox *intFps = new KisIntParseSpinBox(&dlg); intFps->setValue(12); QSpinBox *intTime = new KisIntParseSpinBox(&dlg); intTime->setValue(0); intTime->setMaximum(10000); QSpinBox *intLayer = new KisIntParseSpinBox(&dlg); intLayer->setValue(0); intLayer->setMaximum(100); TimelineFramesView *framesTable = new TimelineFramesView(&dlg); TimelineFramesModel *model = new TimelineFramesModel(&dlg); constructImage(); addSelectionMasks(); m_shapeController->setImage(m_image); m_image->animationInterface()->requestTimeSwitchWithUndo(4); framesTable->setModel(model); model->setDummiesFacade(m_shapeController, m_image); model->setNodeManipulationInterface(new TestingInterface(m_image)); m_layer1->enableAnimation(); m_layer1->setUseInTimeline(true); connect(intFps, SIGNAL(valueChanged(int)), m_image->animationInterface(), SLOT(setFramerate(int))); connect(intTime, SIGNAL(valueChanged(int)), SLOT(setCurrentTime(int))); connect(m_image->animationInterface(), SIGNAL(sigUiTimeChanged(int)), intTime, SLOT(setValue(int))); connect(intLayer, SIGNAL(valueChanged(int)), SLOT(setCurrentLayer(int))); connect(this, SIGNAL(sigRequestNodeChange(KisNodeSP)), model, SLOT(slotCurrentNodeChanged(KisNodeSP))); connect(model, SIGNAL(requestCurrentNodeChanged(KisNodeSP)), this, SLOT(slotGuiChangedNode(KisNodeSP))); QVBoxLayout *layout = new QVBoxLayout(&dlg); layout->addWidget(intFps); layout->addWidget(intTime); layout->addWidget(intLayer); layout->addWidget(framesTable); layout->setStretch(0, 0); layout->setStretch(1, 0); layout->setStretch(2, 0); layout->setStretch(3, 1); dlg.resize(600, 400); dlg.exec(); } void TimelineModelTest::setCurrentTime(int time) { m_image->animationInterface()->requestTimeSwitchWithUndo(time); } KisNodeDummy* findNodeFromRowAny(KisNodeDummy *root, int &startCount) { if (!startCount) { return root; } startCount--; KisNodeDummy *dummy = root->lastChild(); while (dummy) { KisNodeDummy *found = findNodeFromRowAny(dummy, startCount); if (found) return found; dummy = dummy->prevSibling(); } return 0; } void TimelineModelTest::setCurrentLayer(int row) { KisNodeDummy *root = m_shapeController->rootDummy(); KisNodeDummy *dummy = findNodeFromRowAny(root, row); if (!dummy) { qDebug() << "WARNING: Cannot find a node at pos" << row; return; } else { qDebug() << "NonGUI changed active node: " << dummy->node()->name(); } emit sigRequestNodeChange(dummy->node()); } void TimelineModelTest::slotGuiChangedNode(KisNodeSP node) { qDebug() << "GUI changed active node:" << node->name(); } #include "kis_equalizer_column.h" #include "kis_equalizer_slider.h" #include "kis_equalizer_widget.h" void TimelineModelTest::testOnionSkins() { #ifndef ENABLE_GUI_TESTS return; #endif QDialog dlg; QFont font; font.setPointSizeF(9); dlg.setFont(font); QHBoxLayout *layout = new QHBoxLayout(&dlg); KisEqualizerWidget *w = new KisEqualizerWidget(10, &dlg); connect(w, SIGNAL(sigConfigChanged()), SLOT(slotBang())); layout->addWidget(w); dlg.setLayout(layout); dlg.resize(600, 400); dlg.exec(); } void TimelineModelTest::slotBang() { ENTER_FUNCTION() << "!!!!"; } KISTEST_MAIN(TimelineModelTest) diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index 63c3642f7f..2b1f545204 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,988 +1,1021 @@ /* * 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 "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" +#include "KisPart.h" #include +#include "KisDocument.h" +#include "KisViewManager.h" #include "kis_processing_applicator.h" #include #include "kis_node_uuid_info.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool frameHasContent(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return false; // first check if we are a key frame KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return false; return frame->hasContent(); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; nodeInterface->setNodeProperties(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { beginResetModel(); endResetModel(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::slotImageContentChanged() { if (m_d->activeLayerIndex < 0) return; KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); if (!dummy) return; slotDummyChanged(dummy); } void TimelineFramesModel::processUpdateQueue() { Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if (!dummy) { // It's perfectly normal that dummyForNode returns 0; that happens // when views get activated while Krita is closing down. return; } m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameHasContent: { return m_d->frameHasContent(index.row(), index.column()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case KoResourceModel::LargeThumbnailRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; QSize size = dummy->node()->extent().size(); size.scale(maxSize, maxSize, Qt::KeepAspectRatio); if (size.width() == 0 || size.height() == 0) { // No thumbnail can be shown if there isn't width or height... return QVariant(); } QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedMask()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy); } QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, TimelineFramesModel::MimeCopyPolicy copyPolicy) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = baseIndex.row(); const int baseColumn = baseIndex.column(); + const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122(); + stream << int(uuidDataRoot.size()); + stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size()); + stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } stream << index.row() - baseRow << index.column() - baseColumn; const QByteArray uuidData = node->uuid().toRfc4122(); stream << int(uuidData.size()); stream.writeRawData(uuidData.data(), uuidData.size()); } stream << int(copyPolicy); data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); return dropMimeDataExtended(data, action, parent); } bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved) { bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); + int uuidLenRoot = 0; + stream >> uuidLenRoot; + QByteArray uuidDataRoot(uuidLenRoot, '\0'); + stream.readRawData(uuidDataRoot.data(), uuidLenRoot); + QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot); + + KisPart *partInstance = KisPart::instance(); + QList> documents = partInstance->documents(); + + KisImageSP srcImage = 0; + Q_FOREACH(KisDocument *doc, documents) { + KisImageSP tmpSrcImage = doc->image(); + if (tmpSrcImage->root()->uuid() == nodeUuidRoot) { + srcImage = tmpSrcImage; + break; + } + } + + if (!srcImage) { + KisPart *kisPartInstance = KisPart::instance(); + kisPartInstance->currentMainwindow()->viewManager()->showFloatingMessage( + i18n("Dropped frames are not available in this Krita instance") + , QIcon()); + return false; + } + int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); KisAnimationUtils::FrameMovePairList frameMoves; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; const int srcRow = baseRow + relRow; const int srcColumn = baseColumn + relColumn; int uuidLen = 0; stream >> uuidLen; QByteArray uuidData(uuidLen, '\0'); stream.readRawData(uuidData.data(), uuidLen); QUuid nodeUuid = QUuid::fromRfc4122(uuidData); KisNodeSP srcNode; if (!nodeUuid.isNull()) { KisNodeUuidInfo nodeInfo(nodeUuid); - srcNode = nodeInfo.findNode(m_d->image->root()); + srcNode = nodeInfo.findNode(srcImage->root()); } else { QModelIndex index = this->index(srcRow, srcColumn); srcNode = nodeAt(index); } KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; } const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0); if (!dstRowIndex.isValid()) continue; KisNodeSP dstNode = nodeAt(dstRowIndex); KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; } Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisAnimationUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn); KisAnimationUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x()); frameMoves << std::make_pair(srcItem, dstItem); } } MimeCopyPolicy copyPolicy = UndefinedPolicy; if (!stream.atEnd()) { int value = 0; stream >> value; copyPolicy = MimeCopyPolicy(value); } const bool copyFrames = copyPolicy == UndefinedPolicy ? action == Qt::CopyAction : copyPolicy == CopyFramesPolicy; if (dataMoved) { *dataMoved = !copyFrames; } KUndo2Command *cmd = 0; if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } return cmd; } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } bool TimelineFramesModel::insertFrames(int dstColumn, const QList &dstRows, int count, int timing) { if (dstRows.isEmpty() || count <= 0) return true; timing = qMax(timing, 1); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count)); { KisImageBarrierLockerWithFeedback locker(m_d->image); QModelIndexList indexes; Q_FOREACH (int row, dstRows) { for (int column = dstColumn; column < columnCount(); column++) { indexes << index(row, column); } } setLastVisibleFrame(columnCount() + (count * timing) - 1); createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); if (!dummy) continue; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) continue; for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) { KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand); } } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int count) { if (selectedIndexes.isEmpty() || count == 0) return true; QScopedPointer parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QSet uniqueKeyframesInSelection; int minSelectedTime = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; minSelectedTime = qMin(minSelectedTime, index.column()); KisKeyframeSP keyFrame = channel->activeKeyframeAt(index.column()); if (keyFrame) { uniqueKeyframesInSelection.insert(keyFrame); } } QList keyframesToMove; for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) { KisKeyframeSP keyframe = *it; KisKeyframeChannel *channel = keyframe->channel(); KisKeyframeSP nextKeyframe = channel->nextKeyframe(keyframe); if (nextKeyframe) { keyframesToMove << nextKeyframe; } } std::sort(keyframesToMove.begin(), keyframesToMove.end(), [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { return lhs->time() > rhs->time(); }); if (keyframesToMove.isEmpty()) return true; const int maxColumn = columnCount(); if (count > 0) { setLastVisibleFrame(columnCount() + count); } Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { int plannedFrameMove = count; if (count < 0) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0, false); KisKeyframeSP prevFrame = keyframe->channel()->previousKeyframe(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); plannedFrameMove = qMax(count, prevFrame->time() - keyframe->time() + 1); minSelectedTime = qMin(minSelectedTime, prevFrame->time()); } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(keyframe->channel()->node()); KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } const int row = m_d->converter->rowForDummy(dummy); KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; } QModelIndexList indexes; for (int column = keyframe->time(); column < maxColumn; column++) { indexes << index(row, column); } createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, true, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minSelectedTime; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand.data()); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } void TimelineFramesModel::setFullClipRangeStart(int column) { m_d->image->animationInterface()->setFullClipRangeStartTime(column); } void TimelineFramesModel::setFullClipRangeEnd(int column) { m_d->image->animationInterface()->setFullClipRangeEndTime(column); } diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp index bfa840a8b7..a7bc0b6e9f 100644 --- a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp +++ b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp @@ -1,310 +1,310 @@ /* * Copyright (c) 2012 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "compositiondocker_dock.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 "compositionmodel.h" CompositionDockerDock::CompositionDockerDock( ) : QDockWidget(i18n("Compositions")), m_canvas(0) { QWidget* widget = new QWidget(this); setupUi(widget); m_model = new CompositionModel(this); compositionView->setModel(m_model); compositionView->installEventFilter(this); deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); saveButton->setIcon(KisIconUtils::loadIcon("list-add")); exportButton->setIcon(KisIconUtils::loadIcon("document-export")); deleteButton->setToolTip(i18n("Delete Composition")); saveButton->setToolTip(i18n("New Composition")); exportButton->setToolTip(i18n("Export Composition")); setWidget(widget); connect( compositionView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(activated(QModelIndex)) ); compositionView->setContextMenuPolicy(Qt::CustomContextMenu); connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked())); connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked())); connect( exportButton, SIGNAL(clicked(bool)), this, SLOT(exportClicked())); saveNameEdit->setPlaceholderText(i18n("Insert Name")); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(compositionView); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } CompositionDockerDock::~CompositionDockerDock() { } void CompositionDockerDock::setCanvas(KoCanvasBase * canvas) { if (m_canvas && m_canvas->viewManager()) { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->takeAction(action); } } unsetCanvas(); setEnabled(canvas != 0); m_canvas = dynamic_cast(canvas); if (m_canvas && m_canvas->viewManager()) { if (m_actions.isEmpty()) { KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition"); connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition())); m_actions.append(updateAction); KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition"); connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition())); m_actions.append(renameAction); } else { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action); } } updateModel(); } } void CompositionDockerDock::unsetCanvas() { setEnabled(false); m_canvas = 0; m_model->setCompositions(QList()); } void CompositionDockerDock::activated(const QModelIndex& index) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->apply(); } void CompositionDockerDock::deleteClicked() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); m_canvas->viewManager()->image()->removeComposition(composition); updateModel(); } } void CompositionDockerDock::saveClicked() { KisImageWSP image = m_canvas->viewManager()->image(); if (!image) return; // format as 001, 002 ... QString name = saveNameEdit->text(); if (name.isEmpty()) { bool found = false; int i = 1; do { name = QString("%1").arg(i, 3, 10, QChar('0')); found = false; Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (composition->name() == name) { found = true; break; } } i++; } while(found && i < 1000); } KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->store(); image->addComposition(composition); saveNameEdit->clear(); updateModel(); compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0)); image->setModified(); } void CompositionDockerDock::updateModel() { if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { m_model->setCompositions(m_canvas->viewManager()->image()->compositions()); } } void CompositionDockerDock::exportClicked() { if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { QString path; KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock"); dialog.setCaption(i18n("Select a Directory")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); path = dialog.filename(); if (path.isNull()) return; if (!path.endsWith('/')) { path.append('/'); } KisImageWSP image = m_canvas->viewManager()->image(); QString filename = m_canvas->viewManager()->document()->localFilePath(); if (!filename.isEmpty()) { QFileInfo info(filename); - path += info.baseName() + '_'; + path += info.completeBaseName() + '_'; } Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (!composition->isExportEnabled()) { continue; } composition->apply(); image->refreshGraph(); image->lock(); #if 0 image->rootLayer()->projection()->convertToQImage(0, 0, 0, image->width(), image->height()).save(path + composition->name() + ".png"); #else QRect r = image->bounds(); KisDocument *d = KisPart::instance()->createDocument(); KisImageSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name()); dst->setResolution(image->xRes(), image->yRes()); d->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->refreshGraph(); d->setFileBatchMode(true); const QByteArray outputFormat("image/png"); d->exportDocumentSync(QUrl::fromLocalFile(path + composition->name() + ".png"), outputFormat); delete d; #endif image->unlock(); } } } bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { // new index will be set after the method is called QTimer::singleShot(0, this, SLOT(activateCurrentIndex())); } return false; } else { return QObject::eventFilter(obj, event); } } void CompositionDockerDock::activateCurrentIndex() { QModelIndex index = compositionView->currentIndex(); if (index.isValid()) { activated(index); } } void CompositionDockerDock::customContextMenuRequested(QPoint pos) { if (m_actions.isEmpty()) return; QMenu menu; Q_FOREACH (KisAction *action, m_actions) { menu.addAction(action); } menu.exec(compositionView->mapToGlobal(pos)); } void CompositionDockerDock::updateComposition() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->store(); m_canvas->image()->setModified(); } } void CompositionDockerDock::renameComposition() { dbgKrita << "rename"; QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); bool ok; QString name = QInputDialog::getText(this, i18n("Rename Composition"), i18n("New Name:"), QLineEdit::Normal, composition->name(), &ok); if (ok && !name.isEmpty()) { composition->setName(name); m_canvas->image()->setModified(); } } } diff --git a/plugins/dockers/lut/lutdocker_dock.cpp b/plugins/dockers/lut/lutdocker_dock.cpp index e905dc7105..191d75b17f 100644 --- a/plugins/dockers/lut/lutdocker_dock.cpp +++ b/plugins/dockers/lut/lutdocker_dock.cpp @@ -1,683 +1,683 @@ /* * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "lutdocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include "kis_signals_blocker.h" #include "krita_utils.h" #include "ocio_display_filter.h" #include "black_white_point_chooser.h" #include "KisOcioConfiguration.h" #include OCIO::ConstConfigRcPtr defaultRawProfile() { /** * Copied from OCIO, just a noop profile */ const char * INTERNAL_RAW_PROFILE = "ocio_profile_version: 1\n" "strictparsing: false\n" "roles:\n" " default: raw\n" "displays:\n" " sRGB:\n" " - ! {name: Raw, colorspace: raw}\n" "colorspaces:\n" " - !\n" " name: raw\n" " family: raw\n" " equalitygroup:\n" " bitdepth: 32f\n" " isdata: true\n" " allocation: uniform\n" " description: 'A raw color space. Conversions to and from this space are no-ops.'\n"; std::istringstream istream; istream.str(INTERNAL_RAW_PROFILE); return OCIO::Config::CreateFromStream(istream); } LutDockerDock::LutDockerDock() : QDockWidget(i18n("LUT Management")) , m_canvas(0) , m_draggingSlider(false) { using namespace std::placeholders; // For _1 m_exposureCompressor.reset( new KisSignalCompressorWithParam(40, std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1))); m_gammaCompressor.reset( new KisSignalCompressorWithParam(40, std::bind(&LutDockerDock::setCurrentGammaImpl, this, _1))); m_page = new QWidget(this); setupUi(m_page); setWidget(m_page); KisConfig cfg(true); m_chkUseOcio->setChecked(cfg.useOcio()); connect(m_chkUseOcio, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings())); connect(m_colorManagement, SIGNAL(currentIndexChanged(int)), SLOT(slotColorManagementModeChanged())); m_bnSelectConfigurationFile->setToolTip(i18n("Select custom configuration file.")); connect(m_bnSelectConfigurationFile,SIGNAL(clicked()), SLOT(selectOcioConfiguration())); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); m_txtConfigurationPath->setText(ocioOptions.configurationPath); m_txtLut->setText(ocioOptions.lutPath); m_bnSelectLut->setToolTip(i18n("Select LUT file")); connect(m_bnSelectLut, SIGNAL(clicked()), SLOT(selectLut())); connect(m_bnClearLut, SIGNAL(clicked()), SLOT(clearLut())); // See http://groups.google.com/group/ocio-dev/browse_thread/thread/ec95c5f54a74af65 -- maybe need to be reinstated // when people ask for it. m_lblLut->hide(); m_txtLut->hide(); m_bnSelectLut->hide(); m_bnClearLut->hide(); connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(refillViewCombobox())); m_exposureDoubleWidget->setToolTip(i18n("Select the exposure (stops) for HDR images.")); m_exposureDoubleWidget->setRange(-10, 10); m_exposureDoubleWidget->setPrecision(1); m_exposureDoubleWidget->setValue(0.0); m_exposureDoubleWidget->setSingleStep(0.25); m_exposureDoubleWidget->setPageStep(1); connect(m_exposureDoubleWidget, SIGNAL(valueChanged(double)), SLOT(exposureValueChanged(double))); connect(m_exposureDoubleWidget, SIGNAL(sliderPressed()), SLOT(exposureSliderPressed())); connect(m_exposureDoubleWidget, SIGNAL(sliderReleased()), SLOT(exposureSliderReleased())); // Gamma needs to be exponential (gamma *= 1.1f, gamma /= 1.1f as steps) m_gammaDoubleWidget->setToolTip(i18n("Select the amount of gamma modification for display. This does not affect the pixels of your image.")); m_gammaDoubleWidget->setRange(0.1, 5); m_gammaDoubleWidget->setPrecision(2); m_gammaDoubleWidget->setValue(1.0); m_gammaDoubleWidget->setSingleStep(0.1); m_gammaDoubleWidget->setPageStep(1); connect(m_gammaDoubleWidget, SIGNAL(valueChanged(double)), SLOT(gammaValueChanged(double))); connect(m_gammaDoubleWidget, SIGNAL(sliderPressed()), SLOT(gammaSliderPressed())); connect(m_gammaDoubleWidget, SIGNAL(sliderReleased()), SLOT(gammaSliderReleased())); m_bwPointChooser = new BlackWhitePointChooser(this); connect(m_bwPointChooser, SIGNAL(sigBlackPointChanged(qreal)), SLOT(updateDisplaySettings())); connect(m_bwPointChooser, SIGNAL(sigWhitePointChanged(qreal)), SLOT(updateDisplaySettings())); connect(m_btnConvertCurrentColor, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings())); connect(m_btmShowBWConfiguration, SIGNAL(clicked()), SLOT(slotShowBWConfiguration())); slotUpdateIcons(); connect(m_cmbInputColorSpace, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbView, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbLook, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbComponents, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); m_draggingSlider = false; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetOcioConfiguration())); resetOcioConfiguration(); } LutDockerDock::~LutDockerDock() { } void LutDockerDock::setCanvas(KoCanvasBase* _canvas) { if (m_canvas) { m_canvas->disconnect(this); } setEnabled(_canvas != 0); if (KisCanvas2* canvas = dynamic_cast(_canvas)) { m_canvas = canvas; if (m_canvas) { if (!m_canvas->displayFilter()) { resetOcioConfiguration(); updateDisplaySettings(); } else { m_displayFilter = m_canvas->displayFilter(); OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); Q_ASSERT(displayFilter); m_ocioConfig = displayFilter->config; KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget); m_exposureDoubleWidget->setValue(displayFilter->exposure); KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget); m_gammaDoubleWidget->setValue(displayFilter->gamma); KisSignalsBlocker componentsBlocker(m_cmbComponents); m_cmbComponents->setCurrentIndex((int)displayFilter->swizzle); KisSignalsBlocker bwBlocker(m_bwPointChooser); m_bwPointChooser->setBlackPoint(displayFilter->blackPoint); m_bwPointChooser->setWhitePoint(displayFilter->whitePoint); } connect(m_canvas->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged()), Qt::UniqueConnection); connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); } } } void LutDockerDock::unsetCanvas() { m_canvas = 0; setEnabled(false); m_displayFilter = QSharedPointer(0); } void LutDockerDock::slotUpdateIcons() { m_btnConvertCurrentColor->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_btmShowBWConfiguration->setIcon(KisIconUtils::loadIcon("properties")); } void LutDockerDock::slotShowBWConfiguration() { m_bwPointChooser->showPopup(m_btmShowBWConfiguration->mapToGlobal(QPoint())); } bool LutDockerDock::canChangeExposureAndGamma() const { if (!m_chkUseOcio->isChecked() || !m_ocioConfig) return false; const bool externalColorManagementEnabled = m_colorManagement->currentIndex() != (int)KisOcioConfiguration::INTERNAL; #ifdef HAVE_HDR #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace currentColorSpace = KisOpenGLModeProber::instance()->surfaceformatInUse().colorSpace(); #else KisSurfaceColorSpace currentColorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif #endif const bool exposureManagementEnabled = externalColorManagementEnabled #ifdef HAVE_HDR || currentColorSpace == KisSurfaceColorSpace::scRGBColorSpace #endif ; return exposureManagementEnabled; } qreal LutDockerDock::currentExposure() const { if (!m_displayFilter) return 0.0; OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); return canChangeExposureAndGamma() ? displayFilter->exposure : 0.0; } void LutDockerDock::setCurrentExposure(qreal value) { if (!canChangeExposureAndGamma()) return; m_exposureCompressor->start(value); } qreal LutDockerDock::currentGamma() const { if (!m_displayFilter) return 1.0; OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); return canChangeExposureAndGamma() ? displayFilter->gamma : 1.0; } void LutDockerDock::setCurrentGamma(qreal value) { if (!canChangeExposureAndGamma()) return; m_gammaCompressor->start(value); } void LutDockerDock::setCurrentExposureImpl(qreal value) { m_exposureDoubleWidget->setValue(value); if (!m_canvas) return; m_canvas->viewManager()->showFloatingMessage( i18nc("floating message about exposure", "Exposure: %1", KritaUtils::prettyFormatReal(m_exposureDoubleWidget->value())), QIcon(), 500, KisFloatingMessage::Low); } void LutDockerDock::setCurrentGammaImpl(qreal value) { m_gammaDoubleWidget->setValue(value); if (!m_canvas) return; m_canvas->viewManager()->showFloatingMessage( i18nc("floating message about gamma", "Gamma: %1", KritaUtils::prettyFormatReal(m_gammaDoubleWidget->value())), QIcon(), 500, KisFloatingMessage::Low); } void LutDockerDock::slotImageColorSpaceChanged() { enableControls(); writeControls(); resetOcioConfiguration(); } void LutDockerDock::exposureValueChanged(double exposure) { if (m_canvas && !m_draggingSlider) { m_canvas->viewManager()->canvasResourceProvider()->setHDRExposure(exposure); updateDisplaySettings(); } } void LutDockerDock::exposureSliderPressed() { m_draggingSlider = true; } void LutDockerDock::exposureSliderReleased() { m_draggingSlider = false; exposureValueChanged(m_exposureDoubleWidget->value()); } void LutDockerDock::gammaValueChanged(double gamma) { if (m_canvas && !m_draggingSlider) { m_canvas->viewManager()->canvasResourceProvider()->setHDRGamma(gamma); updateDisplaySettings(); } } void LutDockerDock::gammaSliderPressed() { m_draggingSlider = true; } void LutDockerDock::gammaSliderReleased() { m_draggingSlider = false; gammaValueChanged(m_gammaDoubleWidget->value()); } void LutDockerDock::enableControls() { bool canDoExternalColorCorrection = false; if (m_canvas) { KisImageSP image = m_canvas->viewManager()->image(); canDoExternalColorCorrection = image->colorSpace()->colorModelId() == RGBAColorModelID; } if (!canDoExternalColorCorrection) { KisSignalsBlocker colorManagementBlocker(m_colorManagement); Q_UNUSED(colorManagementBlocker); m_colorManagement->setCurrentIndex((int) KisOcioConfiguration::INTERNAL); } const bool ocioEnabled = m_chkUseOcio->isChecked(); m_colorManagement->setEnabled(ocioEnabled && canDoExternalColorCorrection); const bool externalColorManagementEnabled = m_colorManagement->currentIndex() != (int)KisOcioConfiguration::INTERNAL; m_lblInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblView->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbView->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblLook->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbLook->setEnabled(ocioEnabled && externalColorManagementEnabled); const bool exposureManagementEnabled = canChangeExposureAndGamma(); m_exposureDoubleWidget->setEnabled(exposureManagementEnabled); m_gammaDoubleWidget->setEnabled(exposureManagementEnabled); m_lblExposure->setEnabled(exposureManagementEnabled); m_lblGamma->setEnabled(exposureManagementEnabled); QString exposureToolTip; if (!exposureManagementEnabled) { exposureToolTip = i18nc("@info:tooltip", "Exposure and Gamma corrections are disabled in Internal mode. Switch to OCIO mode to use them"); } m_exposureDoubleWidget->setToolTip(exposureToolTip); m_gammaDoubleWidget->setToolTip(exposureToolTip); m_lblExposure->setToolTip(exposureToolTip); m_lblGamma->setToolTip(exposureToolTip); bool enableConfigPath = m_colorManagement->currentIndex() == (int) KisOcioConfiguration::OCIO_CONFIG; lblConfig->setEnabled(ocioEnabled && enableConfigPath); m_txtConfigurationPath->setEnabled(ocioEnabled && enableConfigPath); m_bnSelectConfigurationFile->setEnabled(ocioEnabled && enableConfigPath); } void LutDockerDock::updateDisplaySettings() { if (!m_canvas || !m_canvas->viewManager() || !m_canvas->viewManager()->image()) { return; } enableControls(); writeControls(); if (m_chkUseOcio->isChecked() && m_ocioConfig) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_canvas->displayFilter() || m_canvas->displayFilter() == m_displayFilter); if (!m_displayFilter) { m_displayFilter = m_canvas->displayFilter() ? m_canvas->displayFilter() : QSharedPointer(new OcioDisplayFilter(this)); } OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); displayFilter->config = m_ocioConfig; displayFilter->inputColorSpaceName = m_ocioConfig->getColorSpaceNameByIndex(m_cmbInputColorSpace->currentIndex()); displayFilter->displayDevice = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex()); displayFilter->view = m_ocioConfig->getView(displayFilter->displayDevice, m_cmbView->currentIndex()); displayFilter->look = m_ocioConfig->getLookNameByIndex(m_cmbLook->currentIndex()); displayFilter->gamma = m_gammaDoubleWidget->isEnabled() ? m_gammaDoubleWidget->value() : 1.0; displayFilter->exposure = m_exposureDoubleWidget->isEnabled() ? m_exposureDoubleWidget->value() : 0.0; displayFilter->swizzle = (OCIO_CHANNEL_SWIZZLE)m_cmbComponents->currentIndex(); displayFilter->blackPoint = m_bwPointChooser->blackPoint(); displayFilter->whitePoint = m_bwPointChooser->whitePoint(); displayFilter->forceInternalColorManagement = m_colorManagement->currentIndex() == (int)KisOcioConfiguration::INTERNAL; displayFilter->setLockCurrentColorVisualRepresentation(m_btnConvertCurrentColor->isChecked()); displayFilter->updateProcessor(); m_canvas->setDisplayFilter(m_displayFilter); } else { m_canvas->setDisplayFilter(QSharedPointer(0)); } m_canvas->updateCanvas(); } void LutDockerDock::writeControls() { KisOcioConfiguration ocioOptions; ocioOptions.mode = (KisOcioConfiguration::Mode)m_colorManagement->currentIndex(); ocioOptions.configurationPath = m_txtConfigurationPath->text(); ocioOptions.lutPath = m_txtLut->text(); - ocioOptions.inputColorSpace = m_cmbInputColorSpace->itemHighlighted(); - ocioOptions.displayDevice = m_cmbDisplayDevice->itemHighlighted(); - ocioOptions.displayView = m_cmbView->itemHighlighted(); - ocioOptions.look = m_cmbLook->itemHighlighted(); + ocioOptions.inputColorSpace = m_cmbInputColorSpace->currentUnsqueezedText(); + ocioOptions.displayDevice = m_cmbDisplayDevice->currentUnsqueezedText(); + ocioOptions.displayView = m_cmbView->currentUnsqueezedText(); + ocioOptions.look = m_cmbLook->currentUnsqueezedText(); KisConfig cfg(false); cfg.setUseOcio(m_chkUseOcio->isChecked()); cfg.setOcioConfiguration(ocioOptions); cfg.setOcioLockColorVisualRepresentation(m_btnConvertCurrentColor->isChecked()); } void LutDockerDock::slotColorManagementModeChanged() { enableControls(); writeControls(); resetOcioConfiguration(); } void LutDockerDock::selectOcioConfiguration() { QString filename = m_txtConfigurationPath->text(); KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker"); dialog.setCaption(i18n("Select OpenColorIO Configuration")); dialog.setDefaultDir(QDir::cleanPath(filename)); dialog.setMimeTypeFilters(QStringList() << "application/x-opencolorio-configuration"); filename = dialog.filename(); QFile f(filename); if (f.exists()) { m_txtConfigurationPath->setText(filename); writeControls(); resetOcioConfiguration(); } } void LutDockerDock::resetOcioConfiguration() { KisConfig cfg(true); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); m_ocioConfig.reset(); try { if (ocioOptions.mode == KisOcioConfiguration::INTERNAL) { m_ocioConfig = defaultRawProfile(); } else if (ocioOptions.mode == KisOcioConfiguration::OCIO_ENVIRONMENT) { m_ocioConfig = OCIO::Config::CreateFromEnv(); } else if (ocioOptions.mode == KisOcioConfiguration::OCIO_CONFIG) { QString configFile = ocioOptions.configurationPath; if (QFile::exists(configFile)) { m_ocioConfig = OCIO::Config::CreateFromFile(configFile.toUtf8()); } else { m_ocioConfig = defaultRawProfile(); } } if (m_ocioConfig) { OCIO::SetCurrentConfig(m_ocioConfig); } } catch (OCIO::Exception &exception) { dbgKrita << "OpenColorIO Error:" << exception.what() << "Cannot create the LUT docker"; } if (m_ocioConfig) { refillControls(); } } void LutDockerDock::refillControls() { if (!m_canvas) return; if (!m_canvas->viewManager()) return; if (!m_canvas->viewManager()->canvasResourceProvider()) return; if (!m_canvas->viewManager()->image()) return; KIS_ASSERT_RECOVER_RETURN(m_ocioConfig); KisConfig cfg(true); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); { // Color Management Mode KisSignalsBlocker modeBlocker(m_colorManagement); m_colorManagement->setCurrentIndex((int) ocioOptions.mode); } { // Exposure KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget); m_exposureDoubleWidget->setValue(m_canvas->viewManager()->canvasResourceProvider()->HDRExposure()); } { // Gamma KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget); m_gammaDoubleWidget->setValue(m_canvas->viewManager()->canvasResourceProvider()->HDRGamma()); } { // Components const KoColorSpace *cs = m_canvas->viewManager()->image()->colorSpace(); QStringList itemsList; itemsList << i18n("Luminance"); itemsList << i18n("All Channels"); Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(cs->channels())) { itemsList << channel->name(); } if (m_cmbComponents->originalTexts() != itemsList) { KisSignalsBlocker componentsBlocker(m_cmbComponents); m_cmbComponents->resetOriginalTexts(itemsList); m_cmbComponents->setCurrentIndex(1); // All Channels... } } { // Input Color Space QStringList itemsList; int numOcioColorSpaces = m_ocioConfig->getNumColorSpaces(); for(int i = 0; i < numOcioColorSpaces; ++i) { const char *cs = m_ocioConfig->getColorSpaceNameByIndex(i); OCIO::ConstColorSpaceRcPtr colorSpace = m_ocioConfig->getColorSpace(cs); itemsList << QString::fromUtf8(colorSpace->getName()); } KisSignalsBlocker inputCSBlocker(m_cmbInputColorSpace); if (itemsList != m_cmbInputColorSpace->originalTexts()) { m_cmbInputColorSpace->resetOriginalTexts(itemsList); } m_cmbInputColorSpace->setCurrent(ocioOptions.inputColorSpace); } { // Display Device QStringList itemsList; int numDisplays = m_ocioConfig->getNumDisplays(); for (int i = 0; i < numDisplays; ++i) { itemsList << QString::fromUtf8(m_ocioConfig->getDisplay(i)); } KisSignalsBlocker displayDeviceLocker(m_cmbDisplayDevice); if (itemsList != m_cmbDisplayDevice->originalTexts()) { m_cmbDisplayDevice->resetOriginalTexts(itemsList); } m_cmbDisplayDevice->setCurrent(ocioOptions.displayDevice); } { // Lock Current Color KisSignalsBlocker locker(m_btnConvertCurrentColor); m_btnConvertCurrentColor->setChecked(cfg.ocioLockColorVisualRepresentation()); } refillViewCombobox(); { QStringList itemsList; int numLooks = m_ocioConfig->getNumLooks(); for (int k = 0; k < numLooks; k++) { itemsList << QString::fromUtf8(m_ocioConfig->getLookNameByIndex(k)); } itemsList << i18nc("Item to indicate no look transform being selected","None"); KisSignalsBlocker LookComboLocker(m_cmbLook); if (itemsList != m_cmbLook->originalTexts()) { m_cmbLook->resetOriginalTexts(itemsList); } m_cmbLook->setCurrent(ocioOptions.look); } updateDisplaySettings(); } void LutDockerDock::refillViewCombobox() { KisSignalsBlocker viewComboLocker(m_cmbView); m_cmbView->clear(); if (!m_canvas || !m_ocioConfig) return; const char *display = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex()); int numViews = m_ocioConfig->getNumViews(display); for (int j = 0; j < numViews; ++j) { m_cmbView->addSqueezedItem(QString::fromUtf8(m_ocioConfig->getView(display, j))); } KisConfig cfg(true); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); m_cmbView->setCurrent(ocioOptions.displayView); } void LutDockerDock::selectLut() { QString filename = m_txtLut->text(); KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker"); dialog.setCaption(i18n("Select LUT file")); dialog.setDefaultDir(QDir::cleanPath(filename)); dialog.setMimeTypeFilters(QStringList() << "application/octet-stream", "application/octet-stream"); filename = dialog.filename(); QFile f(filename); if (f.exists() && filename != m_txtLut->text()) { m_txtLut->setText(filename); writeControls(); updateDisplaySettings(); } } void LutDockerDock::clearLut() { m_txtLut->clear(); updateDisplaySettings(); } diff --git a/plugins/dockers/overview/overviewdocker_dock.cpp b/plugins/dockers/overview/overviewdocker_dock.cpp index 2cfa57a2bc..23940a0701 100644 --- a/plugins/dockers/overview/overviewdocker_dock.cpp +++ b/plugins/dockers/overview/overviewdocker_dock.cpp @@ -1,154 +1,156 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that 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 "overviewdocker_dock.h" #include "overviewwidget.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include #include #include #include #include "kis_image.h" #include "kis_paint_device.h" #include "kis_signal_compressor.h" #include "kis_canvas_controller.h" #include "kis_icon_utils.h" #include "kis_signals_blocker.h" OverviewDockerDock::OverviewDockerDock( ) : QDockWidget(i18n("Overview")) , m_zoomSlider(nullptr) , m_rotateSlider(nullptr) , m_mirrorCanvas(nullptr) , m_canvas(nullptr) { QWidget *page = new QWidget(this); m_layout = new QVBoxLayout(page); m_horizontalLayout = new QHBoxLayout(); m_overviewWidget = new OverviewWidget(this); m_overviewWidget->setMinimumHeight(50); m_overviewWidget->setBackgroundRole(QPalette::AlternateBase); m_overviewWidget->setAutoFillBackground(true); // paints background role before paint() m_layout->addWidget(m_overviewWidget, 1); setWidget(page); } void OverviewDockerDock::setCanvas(KoCanvasBase * canvas) { if(m_canvas == canvas) return; setEnabled(canvas != nullptr); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_canvas->image()->disconnect(this); } if (m_zoomSlider) { m_layout->removeWidget(m_zoomSlider); delete m_zoomSlider; m_zoomSlider = nullptr; } if (m_rotateSlider) { m_horizontalLayout->removeWidget(m_rotateSlider); delete m_rotateSlider; m_rotateSlider = nullptr; } if (m_mirrorCanvas) { m_horizontalLayout->removeWidget(m_mirrorCanvas); delete m_mirrorCanvas; m_mirrorCanvas = nullptr; } m_layout->removeItem(m_horizontalLayout); m_canvas = dynamic_cast(canvas); m_overviewWidget->setCanvas(canvas); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->zoomController() && m_canvas->viewManager()->zoomController()->zoomAction()) { m_zoomSlider = m_canvas->viewManager()->zoomController()->zoomAction()->createWidget(m_canvas->imageView()->KisView::statusBar()); m_layout->addWidget(m_zoomSlider); m_rotateSlider = new KisDoubleSliderSpinBox(); m_rotateSlider->setRange(-180, 180, 2); m_rotateSlider->setValue(m_canvas->rotationAngle()); m_rotateSlider->setPrefix(i18n("Rotation: ")); m_rotateSlider->setSuffix("°"); connect(m_rotateSlider, SIGNAL(valueChanged(qreal)), this, SLOT(rotateCanvasView(qreal)), Qt::UniqueConnection); connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(updateSlider())); m_mirrorCanvas = new QToolButton(); QList actions = m_canvas->viewManager()->actionCollection()->actions(); Q_FOREACH(QAction* action, actions) { if (action->objectName()=="mirror_canvas") { m_mirrorCanvas->setDefaultAction(action); } } m_horizontalLayout->addWidget(m_mirrorCanvas); m_horizontalLayout->addWidget(m_rotateSlider); m_layout->addLayout(m_horizontalLayout); } } void OverviewDockerDock::unsetCanvas() { setEnabled(false); m_canvas = nullptr; m_overviewWidget->unsetCanvas(); } void OverviewDockerDock::rotateCanvasView(qreal rotation) { + if (!m_canvas) return; KisCanvasController *canvasController = dynamic_cast(m_canvas->viewManager()->canvasBase()->canvasController()); if (canvasController) { canvasController->rotateCanvas(rotation-m_canvas->rotationAngle()); } } void OverviewDockerDock::updateSlider() { + if (!m_canvas) return; KisSignalsBlocker l(m_rotateSlider); qreal rotation = m_canvas->rotationAngle(); if (rotation > 180) { rotation = rotation - 360; } else if (rotation < -180) { rotation = rotation + 360; } if (m_rotateSlider->value() != rotation) { m_rotateSlider->setValue(rotation); } } diff --git a/plugins/dockers/smallcolorselector/smallcolorselector.cc b/plugins/dockers/smallcolorselector/smallcolorselector.cc index 308bf0ad8b..1d5e4ccaa0 100644 --- a/plugins/dockers/smallcolorselector/smallcolorselector.cc +++ b/plugins/dockers/smallcolorselector/smallcolorselector.cc @@ -1,69 +1,70 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that 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 "smallcolorselector.h" #include #include #include #include "smallcolorselector_dock.h" +#include "opengl/kis_opengl.h" K_PLUGIN_FACTORY_WITH_JSON(SmallColorSelectorPluginFactory, "krita_smallcolorselector.json", registerPlugin();) class SmallColorSelectorDockFactory : public KoDockFactoryBase { public: SmallColorSelectorDockFactory() { } QString id() const override { return QString("SmallColorSelector"); } virtual Qt::DockWidgetArea defaultDockWidgetArea() const { return Qt::RightDockWidgetArea; } QDockWidget* createDockWidget() override { SmallColorSelectorDock * dockWidget = new SmallColorSelectorDock(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; - SmallColorSelectorPlugin::SmallColorSelectorPlugin(QObject *parent, const QVariantList &) : QObject(parent) { - - KoDockRegistry::instance()->add(new SmallColorSelectorDockFactory()); + if (KisOpenGL::hasOpenGL3()) { + KoDockRegistry::instance()->add(new SmallColorSelectorDockFactory()); + } } SmallColorSelectorPlugin::~SmallColorSelectorPlugin() { } #include "smallcolorselector.moc" diff --git a/plugins/dockers/snapshotdocker/SnapshotDocker.cpp b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp index 7db5866650..af6645eb96 100644 --- a/plugins/dockers/snapshotdocker/SnapshotDocker.cpp +++ b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp @@ -1,151 +1,149 @@ /* * Copyright (c) 2019 Tusooa Zhu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SnapshotDocker.h" #include #include #include #include #include #include "KisSnapshotModel.h" #include "KisSnapshotView.h" #include #include #include #include #include #include struct SnapshotDocker::Private { Private(); ~Private(); QScopedPointer model; QPointer view; QPointer canvas; QPointer bnAdd; QPointer bnSwitchTo; QPointer bnRemove; KisSignalAutoConnectionsStore connections; }; SnapshotDocker::Private::Private() : model(new KisSnapshotModel) , view(new KisSnapshotView) , canvas(0) , bnAdd(new QToolButton) , bnSwitchTo(new QToolButton) , bnRemove(new QToolButton) { } SnapshotDocker::Private::~Private() { } SnapshotDocker::SnapshotDocker() : QDockWidget() , m_d(new Private) { QWidget *widget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(widget); m_d->view->setModel(m_d->model.data()); mainLayout->addWidget(m_d->view); QHBoxLayout *buttonsLayout = new QHBoxLayout(widget); m_d->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->bnAdd->setToolTip(i18nc("@info:tooltip", "Create snapshot")); connect(m_d->bnAdd, &QToolButton::clicked, this, &SnapshotDocker::slotBnAddClicked); buttonsLayout->addWidget(m_d->bnAdd); - m_d->bnSwitchTo->setIcon(KisIconUtils::loadIcon("draw-freehand")); /// XXX: which icon to use? + m_d->bnSwitchTo->setIcon(KisIconUtils::loadIcon("snapshot-load")); m_d->bnSwitchTo->setToolTip(i18nc("@info:tooltip", "Switch to selected snapshot")); connect(m_d->bnSwitchTo, &QToolButton::clicked, this, &SnapshotDocker::slotBnSwitchToClicked); buttonsLayout->addWidget(m_d->bnSwitchTo); m_d->bnRemove->setIcon(KisIconUtils::loadIcon("deletelayer")); m_d->bnRemove->setToolTip(i18nc("@info:tooltip", "Remove selected snapshot")); connect(m_d->bnRemove, &QToolButton::clicked, this, &SnapshotDocker::slotBnRemoveClicked); buttonsLayout->addWidget(m_d->bnRemove); mainLayout->addLayout(buttonsLayout); setWidget(widget); setWindowTitle(i18n("Snapshot Docker")); } SnapshotDocker::~SnapshotDocker() { } void SnapshotDocker::setViewManager(KisViewManager *viewManager) { m_d->connections.clear(); KisAction *action = viewManager->actionManager()->createAction("create_snapshot"); m_d->connections.addConnection(action, &KisAction::triggered, m_d->model.data(), &KisSnapshotModel::slotCreateSnapshot); action = viewManager->actionManager()->createAction("switchto_snapshot"); m_d->connections.addConnection(action, &KisAction::triggered, m_d->view, &KisSnapshotView::slotSwitchToSelectedSnapshot); action = viewManager->actionManager()->createAction("remove_snapshot"); m_d->connections.addConnection(action, &KisAction::triggered, m_d->view, &KisSnapshotView::slotRemoveSelectedSnapshot); } void SnapshotDocker::setCanvas(KoCanvasBase *canvas) { KisCanvas2 *c = dynamic_cast(canvas); if (c) { if (m_d->canvas == c) { return; } } m_d->canvas = c; m_d->model->setCanvas(c); } void SnapshotDocker::unsetCanvas() { setCanvas(0); } void SnapshotDocker::slotBnAddClicked() { if (m_d->canvas) { KisAction *action = m_d->canvas->viewManager()->actionManager()->actionByName("create_snapshot"); action->trigger(); } } void SnapshotDocker::slotBnSwitchToClicked() { if (m_d->canvas) { KisAction *action = m_d->canvas->viewManager()->actionManager()->actionByName("switchto_snapshot"); action->trigger(); } } void SnapshotDocker::slotBnRemoveClicked() { if (m_d->canvas) { KisAction *action = m_d->canvas->viewManager()->actionManager()->actionByName("remove_snapshot"); action->trigger(); } } - -#include "SnapshotDocker.moc" diff --git a/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.h b/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.h index 00171e0a4d..22a0b1224c 100644 --- a/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.h +++ b/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.h @@ -1,92 +1,92 @@ /* This file is part of the KDE project * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGSYMBOLCOLLECTIONDOCKER_H #define SVGSYMBOLCOLLECTIONDOCKER_H #include #include #include #include #include #include #include #include #include "ui_WdgSvgCollection.h" class KoSvgSymbolCollectionResource; class SvgCollectionModel : public QAbstractListModel { Q_OBJECT public: explicit SvgCollectionModel(QObject *parent = 0); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; QStringList mimeTypes() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::DropActions supportedDragActions() const override; public: void setSvgSymbolCollectionResource(KoSvgSymbolCollectionResource *resource); private: KoSvgSymbolCollectionResource *m_symbolCollection; }; class SvgSymbolCollectionDockerFactory : public KoDockFactoryBase { public: SvgSymbolCollectionDockerFactory(); QString id() const override; QDockWidget *createDockWidget() override; DockPosition defaultDockPosition() const override { return DockRight; } }; class SvgSymbolCollectionDocker : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT public: explicit SvgSymbolCollectionDocker(QWidget *parent = 0); /// reimplemented void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; public Q_SLOTS: void slotScrollerStateChanged(QScroller::State state){KisKineticScroller::updateCursor(this, state);} private Q_SLOTS: void collectionActivated(int index); void slotSetIconSize(); private: Ui_WdgSvgCollection *m_wdgSvgCollection; QVector m_models; QSlider* m_iconSizeSlider; }; -#endif //KOSHAPECOLLECTIONDOCKER_H +#endif diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp index 362c9e1253..eb705cdc94 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.cpp +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -1,118 +1,118 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_buginfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" DlgBugInfo::DlgBugInfo(QWidget *parent) : KoDialog(parent) { setCaption(i18n("Please paste this information in your bug report")); setButtons(User1 | Ok); setButtonText(User1, i18n("Copy to clipboard")); setDefaultButton(Ok); m_page = new WdgBugInfo(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); QString info; const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log").exists()) { // NOTE: This is intentionally not translated! // Krita version info info.append("Krita"); info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); info.append("\n\n"); info.append("Qt"); info.append("\n Version (compiled): ").append(QT_VERSION_STR); info.append("\n Version (loaded): ").append(qVersion()); info.append("\n\n"); // OS information info.append("OS Information"); info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); info.append("\n Product Type: ").append(QSysInfo::productType()); info.append("\n Product Version: ").append(QSysInfo::productVersion()); info.append("\n\n"); // OpenGL information info.append("\n").append(KisOpenGL::getDebugText()); info.append("\n\n"); // Hardware information info.append("Hardware Information"); info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb"); info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount())); info.append("\n Swap: ").append(KisImageConfig(true).swapDir()); } else { QFile f(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); - f.open(QFile::ReadOnly); + f.open(QFile::ReadOnly | QFile::Text); info = QString::fromUtf8(f.readAll()); f.close(); } // calculate a default height for the widget int wheight = m_page->sizeHint().height(); m_page->txtBugInfo->setText(info); QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); int target_height = fm.height() * info.split('\n').size() + wheight; QDesktopWidget dw; QRect screen_rect = dw.availableGeometry(dw.primaryScreen()); resize(m_page->size().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); connect(this, &KoDialog::user1Clicked, this, [this](){ QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); m_page->txtBugInfo->selectAll(); // feedback }); } DlgBugInfo::~DlgBugInfo() { delete m_page; } diff --git a/plugins/extensions/resourcemanager/resourcemanager.cpp b/plugins/extensions/resourcemanager/resourcemanager.cpp index 7efb4bb064..e6d0423a8e 100644 --- a/plugins/extensions/resourcemanager/resourcemanager.cpp +++ b/plugins/extensions/resourcemanager/resourcemanager.cpp @@ -1,334 +1,334 @@ /* * resourcemanager.cc -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "resourcemanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_bundle_manager.h" #include "dlg_create_bundle.h" #include #include "krita_container_utils.h" class ResourceManager::Private { public: Private() { brushServer = KisBrushServer::instance()->brushServer(); paintopServer = KisResourceServerProvider::instance()->paintOpPresetServer(); gradientServer = KoResourceServerProvider::instance()->gradientServer(); patternServer = KoResourceServerProvider::instance()->patternServer(); paletteServer = KoResourceServerProvider::instance()->paletteServer(); workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); gamutMaskServer = KoResourceServerProvider::instance()->gamutMaskServer(); } KisBrushResourceServer* brushServer; KisPaintOpPresetResourceServer * paintopServer; KoResourceServer* gradientServer; KoResourceServer *patternServer; KoResourceServer* paletteServer; KoResourceServer* workspaceServer; KoResourceServer* gamutMaskServer; }; K_PLUGIN_FACTORY_WITH_JSON(ResourceManagerFactory, "kritaresourcemanager.json", registerPlugin();) ResourceManager::ResourceManager(QObject *parent, const QVariantList &) : KisActionPlugin(parent) , d(new Private()) { KisAction *action = new KisAction(i18n("Import Bundles..."), this); addAction("import_bundles", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportBundles())); action = new KisAction(i18n("Import Brushes..."), this); addAction("import_brushes", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportBrushes())); action = new KisAction(i18n("Import Gradients..."), this); addAction("import_gradients", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportGradients())); action = new KisAction(i18n("Import Palettes..."), this); addAction("import_palettes", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportPalettes())); action = new KisAction(i18n("Import Patterns..."), this); addAction("import_patterns", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportPatterns())); action = new KisAction(i18n("Import Presets..."), this); addAction("import_presets", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportPresets())); action = new KisAction(i18n("Import Workspaces..."), this); addAction("import_workspaces", action); connect(action, SIGNAL(triggered()), this, SLOT(slotImportWorkspaces())); action = new KisAction(i18n("Create Resource Bundle..."), this); addAction("create_bundle", action); connect(action, SIGNAL(triggered()), this, SLOT(slotCreateBundle())); action = new KisAction(i18n("Manage Resources..."), this); addAction("manage_bundles", action); connect(action, SIGNAL(triggered()), this, SLOT(slotManageBundles())); } ResourceManager::~ResourceManager() { } void ResourceManager::slotCreateBundle() { DlgCreateBundle dlgCreateBundle; if (dlgCreateBundle.exec() != QDialog::Accepted) { return; } saveBundle(dlgCreateBundle); } KisResourceBundle *ResourceManager::saveBundle(const DlgCreateBundle &dlgCreateBundle) { QString bundlePath = dlgCreateBundle.saveLocation() + "/" + dlgCreateBundle.bundleName() + ".bundle"; KisResourceBundle *newBundle = new KisResourceBundle(bundlePath); newBundle->addMeta("name", dlgCreateBundle.bundleName()); newBundle->addMeta("author", dlgCreateBundle.authorName()); newBundle->addMeta("email", dlgCreateBundle.email()); newBundle->addMeta("license", dlgCreateBundle.license()); newBundle->addMeta("website", dlgCreateBundle.website()); newBundle->addMeta("description", dlgCreateBundle.description()); newBundle->setThumbnail(dlgCreateBundle.previewImage()); QStringList res = dlgCreateBundle.selectedBrushes(); Q_FOREACH (const QString &r, res) { KoResource *res = d->brushServer->resourceByFilename(r).data(); newBundle->addResource("kis_brushes", res->filename(), d->brushServer->assignedTagsList(res), res->md5()); } res = dlgCreateBundle.selectedGradients(); Q_FOREACH (const QString &r, res) { KoResource *res = d->gradientServer->resourceByFilename(r); newBundle->addResource("ko_gradients", res->filename(), d->gradientServer->assignedTagsList(res), res->md5()); } res = dlgCreateBundle.selectedPalettes(); Q_FOREACH (const QString &r, res) { KoResource *res = d->paletteServer->resourceByFilename(r); newBundle->addResource("ko_palettes", res->filename(), d->paletteServer->assignedTagsList(res), res->md5()); } res = dlgCreateBundle.selectedPatterns(); Q_FOREACH (const QString &r, res) { KoResource *res = d->patternServer->resourceByFilename(r); newBundle->addResource("ko_patterns", res->filename(), d->patternServer->assignedTagsList(res), res->md5()); } res = dlgCreateBundle.selectedPresets(); Q_FOREACH (const QString &r, res) { KisPaintOpPresetSP preset = d->paintopServer->resourceByFilename(r); KoResource *res = preset.data(); newBundle->addResource("kis_paintoppresets", res->filename(), d->paintopServer->assignedTagsList(res), res->md5()); KisPaintOpSettingsSP settings = preset->settings(); QStringList requiredFiles = settings->getStringList(KisPaintOpUtils::RequiredBrushFilesListTag); requiredFiles << settings->getString(KisPaintOpUtils::RequiredBrushFileTag); KritaUtils::makeContainerUnique(requiredFiles); Q_FOREACH (const QString &brushFile, requiredFiles) { KisBrush *brush = d->brushServer->resourceByFilename(brushFile).data(); if (brush) { newBundle->addResource("kis_brushes", brushFile, d->brushServer->assignedTagsList(brush), brush->md5()); } else { qWarning() << "There is no brush with name" << brushFile; } } } res = dlgCreateBundle.selectedWorkspaces(); Q_FOREACH (const QString &r, res) { KoResource *res = d->workspaceServer->resourceByFilename(r); newBundle->addResource("kis_workspaces", res->filename(), d->workspaceServer->assignedTagsList(res), res->md5()); } res = dlgCreateBundle.selectedGamutMasks(); Q_FOREACH (const QString &r, res) { KoResource *res = d->gamutMaskServer->resourceByFilename(r); newBundle->addResource("ko_gamutmasks", res->filename(), d->gamutMaskServer->assignedTagsList(res), res->md5()); } newBundle->addMeta("fileName", bundlePath); newBundle->addMeta("created", QDate::currentDate().toString("dd/MM/yyyy")); if (!newBundle->save()) { QMessageBox::critical(viewManager()->mainWindow(), i18nc("@title:window", "Krita"), i18n("Could not create the new bundle.")); } else { newBundle->setValid(true); if (QDir(KisResourceBundleServerProvider::instance()->resourceBundleServer()->saveLocation()) != QDir(QFileInfo(bundlePath).path())) { newBundle->setFilename(KisResourceBundleServerProvider::instance()->resourceBundleServer()->saveLocation() + "/" + dlgCreateBundle.bundleName() + ".bundle"); } if (KisResourceBundleServerProvider::instance()->resourceBundleServer()->resourceByName(newBundle->name())) { KisResourceBundleServerProvider::instance()->resourceBundleServer()->removeResourceFromServer( KisResourceBundleServerProvider::instance()->resourceBundleServer()->resourceByName(newBundle->name())); } KisResourceBundleServerProvider::instance()->resourceBundleServer()->addResource(newBundle, true); newBundle->load(); } return newBundle; } void ResourceManager::slotManageBundles() { DlgBundleManager* dlg = new DlgBundleManager(this, viewManager()->actionManager()); if (dlg->exec() != QDialog::Accepted) { return; } } QStringList ResourceManager::importResources(const QString &title, const QStringList &mimes) const { KoFileDialog dialog(viewManager()->mainWindow(), KoFileDialog::OpenFiles, "krita_resources"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setCaption(title); dialog.setMimeTypeFilters(mimes); return dialog.filenames(); } void ResourceManager::slotImportBrushes() { QStringList resources = importResources(i18n("Import Brushes"), QStringList() << "image/x-gimp-brush" << "image/x-gimp-x-gimp-brush-animated" << "image/x-adobe-brushlibrary" << "image/png" << "image/svg+xml"); Q_FOREACH (const QString &res, resources) { d->brushServer->importResourceFile(res); } } void ResourceManager::slotImportPresets() { QStringList resources = importResources(i18n("Import Presets"), QStringList() << "application/x-krita-paintoppreset"); Q_FOREACH (const QString &res, resources) { d->paintopServer->importResourceFile(res); } } void ResourceManager::slotImportGradients() { QStringList resources = importResources(i18n("Import Gradients"), QStringList() << "image/svg+xml" << "application/x-gimp-gradient"); Q_FOREACH (const QString &res, resources) { d->gradientServer->importResourceFile(res); } } void ResourceManager::slotImportBundles() { QStringList resources = importResources(i18n("Import Bundles"), QStringList() << "application/x-krita-bundle"); Q_FOREACH (const QString &res, resources) { KisResourceBundle *bundle = KisResourceBundleServerProvider::instance()->resourceBundleServer()->createResource(res); bundle->load(); if (bundle->valid()) { if (!bundle->install()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not install the resources for bundle %1.", res)); } } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not load bundle %1.", res)); } QFileInfo fi(res); - QString newFilename = KisResourceBundleServerProvider::instance()->resourceBundleServer()->saveLocation() + fi.baseName() + bundle->defaultFileExtension(); + QString newFilename = KisResourceBundleServerProvider::instance()->resourceBundleServer()->saveLocation() + fi.completeBaseName() + bundle->defaultFileExtension(); QFileInfo fileInfo(newFilename); int i = 1; while (fileInfo.exists()) { - fileInfo.setFile(KisResourceBundleServerProvider::instance()->resourceBundleServer()->saveLocation() + fi.baseName() + QString("%1").arg(i) + bundle->defaultFileExtension()); + fileInfo.setFile(KisResourceBundleServerProvider::instance()->resourceBundleServer()->saveLocation() + fi.completeBaseName() + QString("%1").arg(i) + bundle->defaultFileExtension()); i++; } bundle->setFilename(fileInfo.filePath()); QFile::copy(res, newFilename); KisResourceBundleServerProvider::instance()->resourceBundleServer()->addResource(bundle, false); } } void ResourceManager::slotImportPatterns() { QStringList resources = importResources(i18n("Import Patterns"), QStringList() << "image/png" << "image/svg+xml" << "application/x-gimp-pattern" << "image/jpeg" << "image/tiff" << "image/bmp" << "image/xpg"); Q_FOREACH (const QString &res, resources) { d->patternServer->importResourceFile(res); } } void ResourceManager::slotImportPalettes() { QStringList resources = importResources(i18n("Import Palettes"), QStringList() << "image/x-gimp-color-palette"); Q_FOREACH (const QString &res, resources) { d->paletteServer->importResourceFile(res); } } void ResourceManager::slotImportWorkspaces() { QStringList resources = importResources(i18n("Import Workspaces"), QStringList() << "application/x-krita-workspace"); Q_FOREACH (const QString &res, resources) { d->workspaceServer->importResourceFile(res); } } #include "resourcemanager.moc" diff --git a/plugins/filters/gradientmap/gradientmap.cpp b/plugins/filters/gradientmap/gradientmap.cpp index eaee269cfb..fcc34d2643 100644 --- a/plugins/filters/gradientmap/gradientmap.cpp +++ b/plugins/filters/gradientmap/gradientmap.cpp @@ -1,134 +1,134 @@ /* * This file is part of the KDE project * * Copyright (c) 2016 Spencer Brown * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QObject" #include "gradientmap.h" #include #include #include "krita_filter_gradient_map.h" #include "KoResourceServerProvider.h" #include "kis_config_widget.h" #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaGradientMapFactory, "kritagradientmap.json", registerPlugin();) KritaGradientMapConfigWidget::KritaGradientMapConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WindowFlags f) : KisConfigWidget(parent, f) { Q_UNUSED(dev) m_page = new WdgGradientMap(this); QHBoxLayout *l = new QHBoxLayout(this); Q_CHECK_PTR(l); l->addWidget(m_page); l->setContentsMargins(0, 0, 0, 0); KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); QSharedPointer gradientResourceAdapter( new KoResourceServerAdapter(serverProvider->gradientServer())); m_gradientChangedCompressor = new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE); m_gradientPopUp = new KoResourcePopupAction(gradientResourceAdapter, m_page->btnGradientChooser); m_activeGradient = KoStopGradient::fromQGradient(dynamic_cast(gradientResourceAdapter->resources().first())->toQGradient()); m_page->gradientEditor->setGradient(m_activeGradient); m_page->gradientEditor->setCompactMode(true); m_page->gradientEditor->setEnabled(true); m_page->btnGradientChooser->setDefaultAction(m_gradientPopUp); m_page->btnGradientChooser->setPopupMode(QToolButton::InstantPopup); connect(m_gradientPopUp, SIGNAL(resourceSelected(QSharedPointer)), this, SLOT(setAbstractGradientToEditor())); connect(m_page->gradientEditor, SIGNAL(sigGradientChanged()), m_gradientChangedCompressor, SLOT(start())); connect(m_gradientChangedCompressor, SIGNAL(timeout()), this, SIGNAL(sigConfigurationItemChanged())); - QObject::connect(m_page->ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); + QObject::connect(m_page->colorModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(m_page->ditherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged); } KritaGradientMapConfigWidget::~KritaGradientMapConfigWidget() { delete m_page; } void KritaGradientMapConfigWidget::setAbstractGradientToEditor() { QSharedPointer bg = qSharedPointerDynamicCast( m_gradientPopUp->currentBackground()); m_activeGradient = KoStopGradient::fromQGradient(bg->gradient()); m_page->gradientEditor->setGradient(m_activeGradient); } KisPropertiesConfigurationSP KritaGradientMapConfigWidget::configuration() const { KisFilterConfigurationSP cfg = new KisFilterConfiguration("gradientmap", 2); if (m_activeGradient) { QDomDocument doc; QDomElement elt = doc.createElement("gradient"); m_activeGradient->toXML(doc, elt); doc.appendChild(elt); cfg->setProperty("gradientXML", doc.toString()); } - cfg->setProperty("ditherEnabled", m_page->ditherGroupBox->isChecked()); + cfg->setProperty("colorMode", m_page->colorModeComboBox->currentIndex()); m_page->ditherWidget->configuration(*cfg, "dither/"); return cfg; } //----------------------------- void KritaGradientMapConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { Q_ASSERT(config); QDomDocument doc; if (config->hasProperty("gradientXML")) { doc.setContent(config->getString("gradientXML", "")); KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement()); if (gradient.stops().size()>0) { m_activeGradient->setStops(gradient.stops()); } } - m_page->ditherGroupBox->setChecked(config->getBool("ditherEnabled")); + m_page->colorModeComboBox->setCurrentIndex(config->getInt("colorMode")); m_page->ditherWidget->setConfiguration(*config, "dither/"); } void KritaGradientMapConfigWidget::setView(KisViewManager *view) { Q_UNUSED(view) } //------------------------------ KritaGradientMap::KritaGradientMap(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KritaFilterGradientMap())); } KritaGradientMap::~KritaGradientMap() { } //----------------------------- #include "gradientmap.moc" diff --git a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp index c66ba1a80d..4b8b6eda03 100644 --- a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp +++ b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp @@ -1,126 +1,136 @@ /* * This file is part of the KDE project * * Copyright (c) 2016 Spencer Brown * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "krita_filter_gradient_map.h" #include #include #include #include #include #include #include "kis_config_widget.h" #include #include #include #include #include #include "gradientmap.h" #include #include KritaFilterGradientMap::KritaFilterGradientMap() : KisFilter(id(), FiltersCategoryMapId, i18n("&Gradient Map...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); setSupportsLevelOfDetail(true); setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsThreading(true); } void KritaFilterGradientMap::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { Q_ASSERT(!device.isNull()); QDomDocument doc; if (config->version()==1) { QDomElement elt = doc.createElement("gradient"); KoAbstractGradient *gradientAb = KoResourceServerProvider::instance()->gradientServer()->resourceByName(config->getString("gradientName")); if (!gradientAb) { qWarning() << "Could not find gradient" << config->getString("gradientName"); } gradientAb = KoResourceServerProvider::instance()->gradientServer()->resources().first(); KoStopGradient::fromQGradient(gradientAb->toQGradient())->toXML(doc, elt); doc.appendChild(elt); } else { doc.setContent(config->getString("gradientXML", "")); } KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement()); - const bool ditherEnabled = config->getBool("ditherEnabled"); + const ColorMode colorMode = ColorMode(config->getInt("colorMode")); KisDitherUtil ditherUtil; - if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/"); + if (colorMode == ColorMode::Dither) ditherUtil.setConfiguration(*config, "dither/"); KoColor outColor(Qt::white, device->colorSpace()); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); qreal grey; const int pixelSize = device->colorSpace()->pixelSize(); while (it.nextPixel()) { grey = qreal(device->colorSpace()->intensity8(it.oldRawData())) / 255; - if (ditherEnabled) { + if (colorMode == ColorMode::Nearest) { + KoGradientStop leftStop, rightStop; + if (!gradient.stopsAt(leftStop, rightStop, grey)) continue; + if (std::abs(grey - leftStop.first) < std::abs(grey - rightStop.first)) { + outColor = leftStop.second; + } + else { + outColor = rightStop.second; + } + } + else if (colorMode == ColorMode::Dither) { KoGradientStop leftStop, rightStop; if (!gradient.stopsAt(leftStop, rightStop, grey)) continue; qreal localT = (grey - leftStop.first) / (rightStop.first - leftStop.first); if (localT < ditherUtil.threshold(QPoint(it.x(), it.y()))) { outColor = leftStop.second; } else { outColor = rightStop.second; } } else { gradient.colorAt(outColor, grey); } outColor.setOpacity(qMin(KoColor(it.oldRawData(), device->colorSpace()).opacityF(), outColor.opacityF())); outColor.convertTo(device->colorSpace()); memcpy(it.rawData(), outColor.data(), pixelSize); } } KisFilterConfigurationSP KritaFilterGradientMap::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("gradientmap", 2); KoAbstractGradient *gradient = KoResourceServerProvider::instance()->gradientServer()->resources().first(); KoStopGradient stopGradient; stopGradient.fromQGradient(gradient->toQGradient()); QDomDocument doc; QDomElement elt = doc.createElement("gradient"); stopGradient.toXML(doc, elt); doc.appendChild(elt); config->setProperty("gradientXML", doc.toString()); - config->setProperty("ditherEnabled", false); + config->setProperty("colorMode", false); KisDitherWidget::factoryConfiguration(*config, "dither/"); return config; } KisConfigWidget * KritaFilterGradientMap::createConfigurationWidget(QWidget * parent, const KisPaintDeviceSP dev, bool) const { return new KritaGradientMapConfigWidget(parent, dev); } diff --git a/plugins/filters/gradientmap/krita_filter_gradient_map.h b/plugins/filters/gradientmap/krita_filter_gradient_map.h index 8171030845..48ea26ea96 100644 --- a/plugins/filters/gradientmap/krita_filter_gradient_map.h +++ b/plugins/filters/gradientmap/krita_filter_gradient_map.h @@ -1,52 +1,57 @@ /* * This file is part of Krita * * Copyright (c) 2016 Spencer Brown * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_GRADIENT_MAP_H #define KIS_GRADIENT_MAP_H #include #include #include #include #include #include #include class KritaFilterGradientMap : public KisFilter { public: KritaFilterGradientMap(); public: + enum ColorMode { + Blend, + Nearest, + Dither, + }; static inline KoID id() { return KoID("gradientmap", i18n("Gradient Map")); } void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override; KisFilterConfigurationSP factoryConfiguration() const override; KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/filters/gradientmap/wdg_gradientmap.ui b/plugins/filters/gradientmap/wdg_gradientmap.ui index f9b7c59850..ed612bc3e2 100644 --- a/plugins/filters/gradientmap/wdg_gradientmap.ui +++ b/plugins/filters/gradientmap/wdg_gradientmap.ui @@ -1,97 +1,156 @@ WdgGradientMap 0 0 - 361 - 341 + 219 + 133 Gradient Map - - + + 0 0 PushButton - - + + - + 0 0 - - - 0 - 0 - + + Color Mode + + + colorModeComboBox - - - - Dither - - - true - - - - - - + + + + + Blend + + + + + Nearest + + + + + Dither + + - - - - Qt::Vertical + + + + + 0 + 0 + + + + QFrame::StyledPanel - - - 20 - 40 - + + 1 - + + Amount: + + + + + + + + + + + + + + + + + + KoColorPopupButton QToolButton
KoColorPopupButton.h
KisStopGradientEditor QWidget
kis_stopgradient_editor.h
1
KisDitherWidget QWidget
KisDitherWidget.h
1
- + + + colorModeComboBox + currentIndexChanged(int) + colorModeStackedWidget + setCurrentIndex(int) + + + 227 + 101 + + + 241 + 121 + + + + + colorModeStackedWidget + currentChanged(int) + colorModeComboBox + setCurrentIndex(int) + + + 297 + 124 + + + 295 + 89 + + + +
diff --git a/plugins/filters/tests/data/guassianhighpass.cfg b/plugins/filters/tests/data/guassianhighpass.cfg new file mode 100644 index 0000000000..9f6f48dde1 --- /dev/null +++ b/plugins/filters/tests/data/guassianhighpass.cfg @@ -0,0 +1,4 @@ + + + 1 + diff --git a/plugins/filters/tests/data/palettize.cfg b/plugins/filters/tests/data/palettize.cfg new file mode 100644 index 0000000000..b804a01583 --- /dev/null +++ b/plugins/filters/tests/data/palettize.cfg @@ -0,0 +1,22 @@ + + + 0.5 + 1189641421 + + 0 + 1 + 0 + true + 0 + 0 + 0 + 0 + 596516649 + 0.125 + + 0 + 1 + 0 + false + + diff --git a/plugins/filters/tests/kis_crash_filter_test.cpp b/plugins/filters/tests/kis_crash_filter_test.cpp index e044e8d527..427157f46e 100644 --- a/plugins/filters/tests/kis_crash_filter_test.cpp +++ b/plugins/filters/tests/kis_crash_filter_test.cpp @@ -1,122 +1,118 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_crash_filter_test.h" #include #include #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "filter/kis_filter.h" #include "kis_pixel_selection.h" #include #include "kis_transaction.h" #include bool KisCrashFilterTest::applyFilter(const KoColorSpace * cs, KisFilterSP f) { QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(new TestUtil::TestingTimedDefaultBounds(qimage.rect())); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); kfc->fromXML(s); } dbgKrita << f->id() << ", " << cs->id() << ", " << cs->profile()->name();// << kfc->toXML() << "\n"; { KisTransaction t(kundo2_noi18n(f->name()), dev); f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); } return true; } bool KisCrashFilterTest::testFilter(KisFilterSP f) { QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); bool ok = false; Q_FOREACH (const KoColorSpace* colorSpace, colorSpaces) { - // XXX: Let's not check the painterly colorspaces right now - if (colorSpace->id().startsWith("KS", Qt::CaseInsensitive)) { - continue; - } // Alpha color spaces are never processed directly. They are // first converted into GrayA color space if (colorSpace->id().startsWith("ALPHA", Qt::CaseInsensitive)) { continue; } ok = applyFilter(colorSpace, f); } return ok; } void KisCrashFilterTest::testCrashFilters() { QStringList excludeFilters; excludeFilters << "colortransfer"; excludeFilters << "gradientmap"; excludeFilters << "phongbumpmap"; excludeFilters << "perchannel"; excludeFilters << "height to normal"; QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (excludeFilters.contains(*it)) continue; if (testFilter(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } #include KISTEST_MAIN(KisCrashFilterTest) diff --git a/plugins/flake/imageshape/ImageShape.cpp b/plugins/flake/imageshape/ImageShape.cpp index aa42692abb..111056a2a7 100644 --- a/plugins/flake/imageshape/ImageShape.cpp +++ b/plugins/flake/imageshape/ImageShape.cpp @@ -1,187 +1,187 @@ /* * 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 #include "ImageShape.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include #include "KisQPainterStateSaver.h" struct Q_DECL_HIDDEN ImageShape::Private { Private() {} Private(const Private &rhs) : image(rhs.image), ratioParser(rhs.ratioParser ? new SvgUtil::PreserveAspectRatioParser(*rhs.ratioParser) : 0), viewBoxTransform(rhs.viewBoxTransform) { } QImage image; QScopedPointer ratioParser; QTransform viewBoxTransform; }; ImageShape::ImageShape() : m_d(new Private) { } ImageShape::ImageShape(const ImageShape &rhs) - : KoTosContainer(new KoTosContainerPrivate(*rhs.d_func(), this)), + : KoTosContainer(rhs), m_d(new Private(*rhs.m_d)) { } ImageShape::~ImageShape() { } KoShape *ImageShape::cloneShape() const { return new ImageShape(*this); } void ImageShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(paintContext); KisQPainterStateSaver saver(&painter); const QRectF myrect(QPointF(), size()); applyConversion(painter, converter); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.setClipRect(QRectF(QPointF(), size()), Qt::IntersectClip); painter.setTransform(m_d->viewBoxTransform, true); painter.drawImage(QPoint(), m_d->image); } void ImageShape::setSize(const QSizeF &size) { KoTosContainer::setSize(size); } void ImageShape::saveOdf(KoShapeSavingContext &context) const { Q_UNUSED(context); } bool ImageShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(element); Q_UNUSED(context); return false; } bool ImageShape::saveSvg(SvgSavingContext &context) { const QString uid = context.createUID("image"); context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", uid); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); context.shapeWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(size().width()))); context.shapeWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(size().height()))); QString aspectString = m_d->ratioParser->toString(); if (!aspectString.isEmpty()) { context.shapeWriter().addAttribute("preserveAspectRatio", aspectString); } QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (m_d->image.save(&buffer, "PNG")) { const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png"); context.shapeWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64()); } context.shapeWriter().endElement(); // image return true; } bool ImageShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x")); const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y")); const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width")); const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height")); setSize(QSizeF(w, h)); setPosition(QPointF(x, y)); if (w == 0.0 || h == 0.0) { setVisible(false); } QString fileName = element.attribute("xlink:href"); QByteArray data; if (fileName.startsWith("data:")) { QRegularExpression re("data:(.+?);base64,(.+)"); QRegularExpressionMatch match = re.match(fileName); data = match.captured(2).toLatin1(); data = QByteArray::fromBase64(data); } else { data = context.fetchExternalFile(fileName); } if (!data.isEmpty()) { QBuffer buffer(&data); m_d->image.load(&buffer, ""); } const QString aspectString = element.attribute("preserveAspectRatio", "xMidYMid meet"); m_d->ratioParser.reset(new SvgUtil::PreserveAspectRatioParser(aspectString)); if (!m_d->image.isNull()) { m_d->viewBoxTransform = QTransform::fromScale(w / m_d->image.width(), h / m_d->image.height()); SvgUtil::parseAspectRatio(*m_d->ratioParser, QRectF(QPointF(), size()), QRect(QPoint(), m_d->image.size()), &m_d->viewBoxTransform); } if (m_d->ratioParser->defer) { // TODO: } return true; } diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp index 30581fca92..b5610d9477 100644 --- a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp +++ b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp @@ -1,554 +1,550 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006,2008 Jan Hambrecht Copyright (C) 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "EllipseShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include EllipseShape::EllipseShape() : m_startAngle(0) , m_endAngle(0) , m_kindAngle(M_PI) , m_type(Arc) { QList handles; handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(0, 50)); setHandles(handles); QSizeF size(100, 100); m_radii = QPointF(size.width() / 2.0, size.height() / 2.0); m_center = QPointF(m_radii.x(), m_radii.y()); updatePath(size); } EllipseShape::EllipseShape(const EllipseShape &rhs) - : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), + : KoParameterShape(rhs), m_startAngle(rhs.m_startAngle), m_endAngle(rhs.m_endAngle), m_kindAngle(rhs.m_kindAngle), m_center(rhs.m_center), m_radii(rhs.m_radii), m_type(rhs.m_type) { } EllipseShape::~EllipseShape() { } KoShape *EllipseShape::cloneShape() const { return new EllipseShape(*this); } void EllipseShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:ellipse"); saveOdfAttributes(context, OdfAllAttributes); switch (m_type) { case Arc: context.xmlWriter().addAttribute("draw:kind", sweepAngle() == 360 ? "full" : "arc"); break; case Pie: context.xmlWriter().addAttribute("draw:kind", "section"); break; case Chord: context.xmlWriter().addAttribute("draw:kind", "cut"); break; default: context.xmlWriter().addAttribute("draw:kind", "full"); } if (m_type != Arc || sweepAngle() != 360) { context.xmlWriter().addAttribute("draw:start-angle", m_startAngle); context.xmlWriter().addAttribute("draw:end-angle", m_endAngle); } saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } else { KoPathShape::saveOdf(context); } } bool EllipseShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { QSizeF size; bool radiusGiven = true; QString kind = element.attributeNS(KoXmlNS::draw, "kind", "full"); if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx")); qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry")); size = QSizeF(2 * rx, 2 * ry); } else if (element.hasAttributeNS(KoXmlNS::svg, "r")) { qreal r = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "r")); size = QSizeF(2 * r, 2 * r); } else { size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); #ifndef NWORKAROUND_ODF_BUGS radiusGiven = KoOdfWorkaround::fixEllipse(kind, context); #else radiusGiven = false; #endif } setSize(size); QPointF pos; if (element.hasAttributeNS(KoXmlNS::svg, "cx") && element.hasAttributeNS(KoXmlNS::svg, "cy")) { qreal cx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cx")); qreal cy = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cy")); pos = QPointF(cx - 0.5 * size.width(), cy - 0.5 * size.height()); } else { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); if (kind == "section") { setType(Pie); } else if (kind == "cut") { setType(Chord); } else { setType(Arc); } setStartAngle(element.attributeNS(KoXmlNS::draw, "start-angle", "0").toDouble()); setEndAngle(element.attributeNS(KoXmlNS::draw, "end-angle", "360").toDouble()); if (!radiusGiven) { // is the size was given by width and height we have to reset the data as the size of the // part of the cut/pie is given. setSize(size); setPosition(pos); } loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements); loadText(element, context); return true; } void EllipseShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_center = matrix.map(m_center); m_radii = matrix.map(m_radii); KoParameterShape::setSize(newSize); } QPointF EllipseShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); m_center = matrix.map(m_center); return offset; } void EllipseShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); QPointF diff(m_center - point); diff.setX(-diff.x()); qreal angle = 0; if (diff.x() == 0) { angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0; } else { diff.setY(diff.y() * m_radii.x() / m_radii.y()); angle = atan(diff.y() / diff.x()); if (angle < 0) { angle += M_PI; } if (diff.y() < 0) { angle += M_PI; } } QList handles = this->handles(); switch (handleId) { case 0: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_startAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 1: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_endAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 2: { QList kindHandlePositions; kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()))); kindHandlePositions.push_back(m_center); kindHandlePositions.push_back((handles[0] + handles[1]) / 2.0); QPointF diff = m_center * 2.0; int handlePos = 0; for (int i = 0; i < kindHandlePositions.size(); ++i) { QPointF pointDiff(p - kindHandlePositions[i]); if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) { diff = pointDiff; handlePos = i; } } handles[handleId] = kindHandlePositions[handlePos]; m_type = EllipseType(handlePos); } break; } setHandles(handles); if (handleId != 2) { updateKindHandle(); } } void EllipseShape::updatePath(const QSizeF &size) { - Q_D(KoParameterShape); - Q_UNUSED(size); QPointF startpoint(handles()[0]); QPointF curvePoints[12]; const qreal distance = sweepAngle(); const bool sameAngles = distance > 359.9; int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, distance, startpoint, curvePoints); KIS_SAFE_ASSERT_RECOVER_RETURN(pointCnt); int curvePointCount = 1 + pointCnt / 3; int requiredPointCount = curvePointCount; if (m_type == Pie) { requiredPointCount++; } else if (m_type == Arc && sameAngles) { curvePointCount--; requiredPointCount--; } createPoints(requiredPointCount); - KoSubpath &points = *d->subpaths[0]; + KoSubpath &points = *subpaths()[0]; int curveIndex = 0; points[0]->setPoint(startpoint); points[0]->removeControlPoint1(); points[0]->setProperty(KoPathPoint::StartSubpath); for (int i = 1; i < curvePointCount; ++i) { points[i - 1]->setControlPoint2(curvePoints[curveIndex++]); points[i]->setControlPoint1(curvePoints[curveIndex++]); points[i]->setPoint(curvePoints[curveIndex++]); points[i]->removeControlPoint2(); } if (m_type == Pie) { points[requiredPointCount - 1]->setPoint(m_center); points[requiredPointCount - 1]->removeControlPoint1(); points[requiredPointCount - 1]->removeControlPoint2(); } else if (m_type == Arc && sameAngles) { points[curvePointCount - 1]->setControlPoint2(curvePoints[curveIndex]); points[0]->setControlPoint1(curvePoints[++curveIndex]); } for (int i = 0; i < requiredPointCount; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } - d->subpaths[0]->last()->setProperty(KoPathPoint::StopSubpath); + subpaths()[0]->last()->setProperty(KoPathPoint::StopSubpath); if (m_type == Arc && !sameAngles) { - d->subpaths[0]->first()->unsetProperty(KoPathPoint::CloseSubpath); - d->subpaths[0]->last()->unsetProperty(KoPathPoint::CloseSubpath); + subpaths()[0]->first()->unsetProperty(KoPathPoint::CloseSubpath); + subpaths()[0]->last()->unsetProperty(KoPathPoint::CloseSubpath); } else { - d->subpaths[0]->first()->setProperty(KoPathPoint::CloseSubpath); - d->subpaths[0]->last()->setProperty(KoPathPoint::CloseSubpath); + subpaths()[0]->first()->setProperty(KoPathPoint::CloseSubpath); + subpaths()[0]->last()->setProperty(KoPathPoint::CloseSubpath); } notifyPointsChanged(); normalize(); } void EllipseShape::createPoints(int requiredPointCount) { - Q_D(KoParameterShape); - - if (d->subpaths.count() != 1) { + if (subpaths().count() != 1) { clear(); - d->subpaths.append(new KoSubpath()); + subpaths().append(new KoSubpath()); } - int currentPointCount = d->subpaths[0]->count(); + int currentPointCount = subpaths()[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { - delete d->subpaths[0]->front(); - d->subpaths[0]->pop_front(); + delete subpaths()[0]->front(); + subpaths()[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { - d->subpaths[0]->append(new KoPathPoint(this, QPointF())); + subpaths()[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } void EllipseShape::updateKindHandle() { qreal angle = 0.5 * (m_startAngle + m_endAngle); if (m_startAngle > m_endAngle) { angle += 180.0; } m_kindAngle = normalizeAngle(kisDegreesToRadians(angle)); QList handles = this->handles(); switch (m_type) { case Arc: handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()); break; case Pie: handles[2] = m_center; break; case Chord: handles[2] = (handles[0] + handles[1]) / 2.0; break; } setHandles(handles); } void EllipseShape::updateAngleHandles() { qreal startRadian = kisDegreesToRadians(normalizeAngleDegrees(m_startAngle)); qreal endRadian = kisDegreesToRadians(normalizeAngleDegrees(m_endAngle)); QList handles = this->handles(); handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y()); handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y()); setHandles(handles); } qreal EllipseShape::sweepAngle() const { const qreal a1 = normalizeAngle(kisDegreesToRadians(m_startAngle)); const qreal a2 = normalizeAngle(kisDegreesToRadians(m_endAngle)); qreal sAngle = a2 - a1; if (a1 > a2) { sAngle = 2 * M_PI + sAngle; } if (qAbs(a1 - a2) < 0.05 / M_PI) { sAngle = 2 * M_PI; } return kisRadiansToDegrees(sAngle); } void EllipseShape::setType(EllipseType type) { m_type = type; updateKindHandle(); updatePath(size()); } EllipseShape::EllipseType EllipseShape::type() const { return m_type; } void EllipseShape::setStartAngle(qreal angle) { m_startAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::startAngle() const { return m_startAngle; } void EllipseShape::setEndAngle(qreal angle) { m_endAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::endAngle() const { return m_endAngle; } QString EllipseShape::pathShapeId() const { return EllipseShapeId; } bool EllipseShape::saveSvg(SvgSavingContext &context) { // let basic path saiving code handle our saving if (!isParametricShape()) return false; if (type() == EllipseShape::Arc && startAngle() == endAngle()) { const QSizeF size = this->size(); const bool isCircle = size.width() == size.height(); context.shapeWriter().startElement(isCircle ? "circle" : "ellipse"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); if (isCircle) { context.shapeWriter().addAttribute("r", 0.5 * size.width()); } else { context.shapeWriter().addAttribute("rx", 0.5 * size.width()); context.shapeWriter().addAttribute("ry", 0.5 * size.height()); } context.shapeWriter().addAttribute("cx", 0.5 * size.width()); context.shapeWriter().addAttribute("cy", 0.5 * size.height()); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } else { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); context.shapeWriter().addAttribute("sodipodi:type", "arc"); context.shapeWriter().addAttribute("sodipodi:rx", m_radii.x()); context.shapeWriter().addAttribute("sodipodi:ry", m_radii.y()); context.shapeWriter().addAttribute("sodipodi:cx", m_center.x()); context.shapeWriter().addAttribute("sodipodi:cy", m_center.y()); context.shapeWriter().addAttribute("sodipodi:start", 2 * M_PI - kisDegreesToRadians(endAngle())); context.shapeWriter().addAttribute("sodipodi:end", 2 * M_PI - kisDegreesToRadians(startAngle())); switch (type()) { case Pie: // noop break; case Chord: context.shapeWriter().addAttribute("sodipodi:arc-type", "chord"); break; case Arc: context.shapeWriter().addAttribute("sodipodi:open", "true"); break; } context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform())); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } return true; } bool EllipseShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { qreal rx = 0, ry = 0; qreal cx = 0; qreal cy = 0; qreal start = 0; qreal end = 0; EllipseType type = Arc; const QString extendedNamespace = element.attribute("sodipodi:type") == "arc" ? "sodipodi" : element.attribute("krita:type") == "arc" ? "krita" : ""; if (element.tagName() == "ellipse") { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute("ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "circle") { rx = ry = SvgUtil::parseUnitXY(context.currentGC(), element.attribute("r")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "path" && !extendedNamespace.isEmpty()) { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":cy", "0")); start = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":end")); end = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":start")); const QString kritaArcType = element.attribute("sodipodi:arc-type", element.attribute("krita:arcType")); if (kritaArcType.isEmpty()) { if (element.attribute("sodipodi:open", "false") == "false") { type = Pie; } } else if (kritaArcType == "pie") { type = Pie; } else if (kritaArcType == "chord") { type = Chord; } } else { return false; } setSize(QSizeF(2 * rx, 2 * ry)); setPosition(QPointF(cx - rx, cy - ry)); if (rx == 0.0 || ry == 0.0) { setVisible(false); } if (start != 0 || start != end) { setStartAngle(kisRadiansToDegrees(start)); setEndAngle(kisRadiansToDegrees(end)); setType(type); } return true; } diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp index 3c246c9c43..5df78c5e23 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp @@ -1,744 +1,740 @@ /* This file is part of the KDE project * Copyright (C) 2007,2010,2011 Jan Hambrecht * Copyright (C) 2009-2010 Thomas Zander * Copyright (C) 2010 Carlos Licea * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Contact: Suresh Chande suresh.chande@nokia.com * Copyright (C) 2009-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 #include "EnhancedPathShape.h" #include "EnhancedPathCommand.h" #include "EnhancedPathParameter.h" #include "EnhancedPathHandle.h" #include "EnhancedPathFormula.h" #include #include #include #include #include #include #include EnhancedPathShape::EnhancedPathShape(const QRect &viewBox) : m_viewBox(viewBox) , m_viewBoxOffset(0.0, 0.0) , m_mirrorVertically(false) , m_mirrorHorizontally(false) , m_pathStretchPointX(-1) , m_pathStretchPointY(-1) , m_cacheResults(false) { } EnhancedPathShape::EnhancedPathShape(const EnhancedPathShape &rhs) - : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), + : KoParameterShape(rhs), m_viewBox(rhs.m_viewBox), m_viewBound(rhs.m_viewBound), m_viewMatrix(rhs.m_viewMatrix), m_mirrorMatrix(rhs.m_mirrorMatrix), m_viewBoxOffset(rhs.m_viewBoxOffset), m_textArea(rhs.m_textArea), m_commands(rhs.m_commands), m_enhancedHandles(rhs.m_enhancedHandles), m_formulae(rhs.m_formulae), m_modifiers(rhs.m_modifiers), m_parameters(rhs.m_parameters), m_mirrorVertically(rhs.m_mirrorVertically), m_mirrorHorizontally(rhs.m_mirrorHorizontally), m_pathStretchPointX(rhs.m_pathStretchPointX), m_pathStretchPointY(rhs.m_pathStretchPointY), m_resultCache(rhs.m_resultCache), m_cacheResults(rhs.m_cacheResults) { } EnhancedPathShape::~EnhancedPathShape() { reset(); } KoShape *EnhancedPathShape::cloneShape() const { return new EnhancedPathShape(*this); } void EnhancedPathShape::reset() { qDeleteAll(m_commands); m_commands.clear(); qDeleteAll(m_enhancedHandles); m_enhancedHandles.clear(); setHandles(QList()); qDeleteAll(m_formulae); m_formulae.clear(); qDeleteAll(m_parameters); m_parameters.clear(); m_modifiers.clear(); m_viewMatrix.reset(); m_viewBoxOffset = QPointF(); clear(); m_textArea.clear(); } void EnhancedPathShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); EnhancedPathHandle *handle = m_enhancedHandles[ handleId ]; if (handle) { handle->changePosition(shapeToViewbox(point)); } } void EnhancedPathShape::updatePath(const QSizeF &size) { - Q_D(KoParameterShape); - if (isParametricShape()) { clear(); enableResultCache(true); foreach (EnhancedPathCommand *cmd, m_commands) { cmd->execute(); } enableResultCache(false); qreal stretchPointsScale = 1; bool isStretched = useStretchPoints(size, stretchPointsScale); m_viewBound = outline().boundingRect(); m_mirrorMatrix.reset(); m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y()); m_mirrorMatrix.scale(m_mirrorHorizontally ? -1 : 1, m_mirrorVertically ? -1 : 1); m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y()); QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y()); // if stretch points are set than stretch the path manually if (isStretched) { //if the path was stretched manually the stretch matrix is not more valid //and it has to be recalculated so that stretching in x and y direction is the same matrix.scale(stretchPointsScale, stretchPointsScale); matrix = m_mirrorMatrix * matrix; } else { matrix = m_mirrorMatrix * m_viewMatrix * matrix; } - foreach (KoSubpath *subpath, d->subpaths) { + foreach (KoSubpath *subpath, subpaths()) { foreach (KoPathPoint *point, *subpath) { point->map(matrix); } } const int handleCount = m_enhancedHandles.count(); QList handles; for (int i = 0; i < handleCount; ++i) { handles.append(matrix.map(m_enhancedHandles[i]->position())); } setHandles(handles); normalize(); } } void EnhancedPathShape::setSize(const QSizeF &newSize) { // handle offset KoParameterShape::setSize(newSize); // calculate scaling factors from viewbox size to shape size qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width() / m_viewBound.width(); qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height() / m_viewBound.height(); // create view matrix, take mirroring into account m_viewMatrix.reset(); m_viewMatrix.scale(xScale, yScale); updatePath(newSize); } QPointF EnhancedPathShape::normalize() { QPointF offset = KoParameterShape::normalize(); m_viewBoxOffset -= offset; return offset; } QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const { return (m_mirrorMatrix * m_viewMatrix).inverted().map(point - m_viewBoxOffset); } void EnhancedPathShape::evaluateHandles() { const int handleCount = m_enhancedHandles.count(); QList handles; for (int i = 0; i < handleCount; ++i) { handles.append(m_enhancedHandles[i]->position()); } setHandles(handles); } QRect EnhancedPathShape::viewBox() const { return m_viewBox; } qreal EnhancedPathShape::evaluateReference(const QString &reference) { if (reference.isEmpty()) { return 0.0; } const char c = reference[0].toLatin1(); qreal res = 0.0; switch (c) { // referenced modifier case '$': { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); res = m_modifiers.value(modifierIndex); break; } // referenced formula case '?': { QString fname = reference.mid(1); if (m_cacheResults && m_resultCache.contains(fname)) { res = m_resultCache.value(fname); } else { FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname); if (formulaIt != m_formulae.constEnd()) { EnhancedPathFormula *formula = formulaIt.value(); if (formula) { res = formula->evaluate(); if (m_cacheResults) { m_resultCache.insert(fname, res); } } } } break; } // maybe an identifier ? default: EnhancedPathNamedParameter p(reference, this); res = p.evaluate(); break; } return res; } qreal EnhancedPathShape::evaluateConstantOrReference(const QString &val) { bool ok = true; qreal res = val.toDouble(&ok); if (ok) { return res; } return evaluateReference(val); } void EnhancedPathShape::modifyReference(const QString &reference, qreal value) { if (reference.isEmpty()) { return; } const char c = reference[0].toLatin1(); if (c == '$') { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) { m_modifiers[modifierIndex] = value; } } } EnhancedPathParameter *EnhancedPathShape::parameter(const QString &text) { Q_ASSERT(! text.isEmpty()); ParameterStore::const_iterator parameterIt = m_parameters.constFind(text); if (parameterIt != m_parameters.constEnd()) { return parameterIt.value(); } else { EnhancedPathParameter *parameter = 0; const char c = text[0].toLatin1(); if (c == '$' || c == '?') { parameter = new EnhancedPathReferenceParameter(text, this); } else { bool success = false; qreal constant = text.toDouble(&success); if (success) { parameter = new EnhancedPathConstantParameter(constant, this); } else { Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text); if (identifier != IdentifierUnknown) { parameter = new EnhancedPathNamedParameter(identifier, this); } } } if (parameter) { m_parameters[text] = parameter; } return parameter; } } void EnhancedPathShape::addFormula(const QString &name, const QString &formula) { if (name.isEmpty() || formula.isEmpty()) { return; } m_formulae[name] = new EnhancedPathFormula(formula, this); } void EnhancedPathShape::addHandle(const QMap &handle) { if (handle.isEmpty()) { return; } if (!handle.contains("draw:handle-position")) { return; } QVariant position = handle.value("draw:handle-position"); QStringList tokens = position.toString().simplified().split(' '); if (tokens.count() < 2) { return; } EnhancedPathHandle *newHandle = new EnhancedPathHandle(this); newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1])); // check if we have a polar handle if (handle.contains("draw:handle-polar")) { QVariant polar = handle.value("draw:handle-polar"); QStringList tokens = polar.toString().simplified().split(' '); if (tokens.count() == 2) { newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1])); QVariant minRadius = handle.value("draw:handle-radius-range-minimum"); QVariant maxRadius = handle.value("draw:handle-radius-range-maximum"); if (minRadius.isValid() && maxRadius.isValid()) { newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString())); } } } else { QVariant minX = handle.value("draw:handle-range-x-minimum"); QVariant maxX = handle.value("draw:handle-range-x-maximum"); if (minX.isValid() && maxX.isValid()) { newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString())); } QVariant minY = handle.value("draw:handle-range-y-minimum"); QVariant maxY = handle.value("draw:handle-range-y-maximum"); if (minY.isValid() && maxY.isValid()) { newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString())); } } m_enhancedHandles.append(newHandle); evaluateHandles(); } void EnhancedPathShape::addModifiers(const QString &modifiers) { if (modifiers.isEmpty()) { return; } QStringList tokens = modifiers.simplified().split(' '); int tokenCount = tokens.count(); for (int i = 0; i < tokenCount; ++i) { m_modifiers.append(tokens[i].toDouble()); } } void EnhancedPathShape::addCommand(const QString &command) { addCommand(command, true); } void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate) { QString commandStr = command.simplified(); if (commandStr.isEmpty()) { return; } // the first character is the command EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this); // strip command char commandStr = commandStr.mid(1).simplified(); // now parse the command parameters if (!commandStr.isEmpty()) { QStringList tokens = commandStr.split(' '); for (int i = 0; i < tokens.count(); ++i) { cmd->addParameter(parameter(tokens[i])); } } m_commands.append(cmd); if (triggerUpdate) { updatePath(size()); } } bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale) { - Q_D(KoParameterShape); - bool retval = false; if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) { qreal scaleX = size.width(); qreal scaleY = size.height(); if (qreal(m_viewBox.width()) / m_viewBox.height() < qreal(scaleX) / scaleY) { qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width(); - foreach (KoSubpath *subpath, d->subpaths) { + foreach (KoSubpath *subpath, subpaths()) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().x() >= m_pathStretchPointX && currPoint->controlPoint1().x() >= m_pathStretchPointX && currPoint->controlPoint2().x() >= m_pathStretchPointX) { currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y())); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX, currPoint->controlPoint1().y())); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX, currPoint->controlPoint2().y())); retval = true; } } } scale = scaleY / m_viewBox.height(); } else if (qreal(m_viewBox.width()) / m_viewBox.height() > qreal(scaleX) / scaleY) { qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height(); - foreach (KoSubpath *subpath, d->subpaths) { + foreach (KoSubpath *subpath, subpaths()) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().y() >= m_pathStretchPointY && currPoint->controlPoint1().y() >= m_pathStretchPointY && currPoint->controlPoint2().y() >= m_pathStretchPointY) { currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY)); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(), currPoint->controlPoint1().y() + deltaY)); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(), currPoint->controlPoint2().y() + deltaY)); retval = true; } } } scale = scaleX / m_viewBox.width(); } notifyPointsChanged(); } return retval; } void EnhancedPathShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:custom-shape"); const QSizeF currentSize = outline().boundingRect().size(); // save the right position so that when loading we fit the viewbox // to the right position without getting any wrong scaling // -> calculate the right position from the current 0 position / viewbound ratio // this is e.g. the case when there is a callout that goes into negative viewbound coordinates QPointF topLeft = m_viewBound.topLeft(); QPointF diff; if (qAbs(topLeft.x()) > 1E-5) { diff.setX(topLeft.x()*currentSize.width() / m_viewBound.width()); } if (qAbs(topLeft.y()) > 1E-5) { diff.setY(topLeft.y()*currentSize.height() / m_viewBound.height()); } if (diff.isNull()) { saveOdfAttributes(context, OdfAllAttributes & ~OdfSize); } else { //FIXME: this needs to be fixed for shapes that are transformed by rotation or skewing QTransform offset(context.shapeOffset(this)); QTransform newOffset(offset); newOffset.translate(-diff.x(), -diff.y()); context.addShapeOffset(this, newOffset); saveOdfAttributes(context, OdfAllAttributes & ~OdfSize); if (offset.isIdentity()) { context.removeShapeOffset(this); } else { context.addShapeOffset(this, offset); } } // save the right size so that when loading we fit the viewbox // to the right size without getting any wrong scaling // -> calculate the right size from the current size/viewbound ratio context.xmlWriter().addAttribute("svg:width", currentSize.width() == 0 ? 0 : m_viewBox.width()*currentSize.width() / m_viewBound.width()); context.xmlWriter().addAttribute("svg:height", currentSize.height() == 0 ? 0 : m_viewBox.height()*currentSize.height() / m_viewBound.height()); saveText(context); context.xmlWriter().startElement("draw:enhanced-geometry"); context.xmlWriter().addAttribute("svg:viewBox", QString("%1 %2 %3 %4").arg(m_viewBox.x()).arg(m_viewBox.y()).arg(m_viewBox.width()).arg(m_viewBox.height())); if (m_pathStretchPointX != -1) { context.xmlWriter().addAttribute("draw:path-stretchpoint-x", m_pathStretchPointX); } if (m_pathStretchPointY != -1) { context.xmlWriter().addAttribute("draw:path-stretchpoint-y", m_pathStretchPointY); } if (m_mirrorHorizontally) { context.xmlWriter().addAttribute("draw:mirror-horizontal", "true"); } if (m_mirrorVertically) { context.xmlWriter().addAttribute("draw:mirror-vertical", "true"); } QString modifiers; foreach (qreal modifier, m_modifiers) { modifiers += QString::number(modifier) + ' '; } context.xmlWriter().addAttribute("draw:modifiers", modifiers.trimmed()); if (m_textArea.size() >= 4) { context.xmlWriter().addAttribute("draw:text-areas", m_textArea.join(" ")); } QString path; foreach (EnhancedPathCommand *c, m_commands) { path += c->toString() + ' '; } context.xmlWriter().addAttribute("draw:enhanced-path", path.trimmed()); FormulaStore::const_iterator i = m_formulae.constBegin(); for (; i != m_formulae.constEnd(); ++i) { context.xmlWriter().startElement("draw:equation"); context.xmlWriter().addAttribute("draw:name", i.key()); context.xmlWriter().addAttribute("draw:formula", i.value()->toString()); context.xmlWriter().endElement(); // draw:equation } foreach (EnhancedPathHandle *handle, m_enhancedHandles) { handle->saveOdf(context); } context.xmlWriter().endElement(); // draw:enhanced-geometry saveOdfCommonChildElements(context); context.xmlWriter().endElement(); // draw:custom-shape } else { KoPathShape::saveOdf(context); } } bool EnhancedPathShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { reset(); const KoXmlElement enhancedGeometry(KoXml::namedItemNS(element, KoXmlNS::draw, "enhanced-geometry")); if (!enhancedGeometry.isNull()) { setPathStretchPointX(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-x", "-1").toDouble()); setPathStretchPointY(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-y", "-1").toDouble()); // load the modifiers QString modifiers = enhancedGeometry.attributeNS(KoXmlNS::draw, "modifiers", ""); if (!modifiers.isEmpty()) { addModifiers(modifiers); } m_textArea = enhancedGeometry.attributeNS(KoXmlNS::draw, "text-areas", "").split(' '); if (m_textArea.size() >= 4) { setResizeBehavior(TextFollowsPreferredTextRect); } KoXmlElement grandChild; forEachElement(grandChild, enhancedGeometry) { if (grandChild.namespaceURI() != KoXmlNS::draw) { continue; } if (grandChild.localName() == "equation") { QString name = grandChild.attributeNS(KoXmlNS::draw, "name"); QString formula = grandChild.attributeNS(KoXmlNS::draw, "formula"); addFormula(name, formula); } else if (grandChild.localName() == "handle") { EnhancedPathHandle *handle = new EnhancedPathHandle(this); if (handle->loadOdf(grandChild, context)) { m_enhancedHandles.append(handle); evaluateHandles(); } else { delete handle; } } } setMirrorHorizontally(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-horizontal") == "true"); setMirrorVertically(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-vertical") == "true"); // load the enhanced path data QString path = enhancedGeometry.attributeNS(KoXmlNS::draw, "enhanced-path", ""); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixEnhancedPath(path, enhancedGeometry, context); #endif // load the viewbox m_viewBox = loadOdfViewbox(enhancedGeometry); if (!path.isEmpty()) { parsePathData(path); } if (m_viewBox.isEmpty()) { // if there is no view box defined make it is big as the path. m_viewBox = m_viewBound.toAlignedRect(); } } QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // the viewbox is to be fitted into the size of the shape, so before setting // the size we just loaded // we set the viewbox to be the basis to calculate // the viewbox matrix from m_viewBound = m_viewBox; setSize(size); QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos - m_viewMatrix.map(QPointF(0, 0)) - m_viewBoxOffset); loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements); loadText(element, context); return true; } void EnhancedPathShape::parsePathData(const QString &data) { if (data.isEmpty()) { return; } int start = -1; bool separator = true; for (int i = 0; i < data.length(); ++i) { QChar ch = data.at(i); ushort uni_ch = ch.unicode(); if (separator && (uni_ch == 'M' || uni_ch == 'L' || uni_ch == 'C' || uni_ch == 'Z' || uni_ch == 'N' || uni_ch == 'F' || uni_ch == 'S' || uni_ch == 'T' || uni_ch == 'U' || uni_ch == 'A' || uni_ch == 'B' || uni_ch == 'W' || uni_ch == 'V' || uni_ch == 'X' || uni_ch == 'Y' || uni_ch == 'Q')) { if (start != -1) { // process last chars addCommand(data.mid(start, i - start), false); } start = i; } separator = ch.isSpace(); } if (start < data.length()) { addCommand(data.mid(start)); } if (start != -1) { updatePath(size()); } } void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally) { if (m_mirrorHorizontally != mirrorHorizontally) { m_mirrorHorizontally = mirrorHorizontally; updatePath(size()); } } void EnhancedPathShape::setMirrorVertically(bool mirrorVertically) { if (m_mirrorVertically != mirrorVertically) { m_mirrorVertically = mirrorVertically; updatePath(size()); } } void EnhancedPathShape::shapeChanged(ChangeType type, KoShape *shape) { KoParameterShape::shapeChanged(type, shape); if (!shape || shape == this) { if (type == ParentChanged || type == ParameterChanged) { updateTextArea(); } } } void EnhancedPathShape::updateTextArea() { if (m_textArea.size() >= 4) { QRectF r = m_viewBox; r.setLeft(evaluateConstantOrReference(m_textArea[0])); r.setTop(evaluateConstantOrReference(m_textArea[1])); r.setRight(evaluateConstantOrReference(m_textArea[2])); r.setBottom(evaluateConstantOrReference(m_textArea[3])); r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset); setPreferredTextRect(r); } } void EnhancedPathShape::enableResultCache(bool enable) { m_resultCache.clear(); m_cacheResults = enable; } void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX) { if (m_pathStretchPointX != pathStretchPointX) { m_pathStretchPointX = pathStretchPointX; } } void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY) { if (m_pathStretchPointY != pathStretchPointY) { m_pathStretchPointY = pathStretchPointY; } } diff --git a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp index 6e0de7e4d3..bd21d994ea 100644 --- a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp +++ b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp @@ -1,387 +1,384 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2008 Jan Hambrecht Copyright (C) 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "RectangleShape.h" #include #include #include #include #include #include #include #include #include #include #include RectangleShape::RectangleShape() - : m_cornerRadiusX(0) + : KoParameterShape() + , m_cornerRadiusX(0) , m_cornerRadiusY(0) { QList handles; handles.push_back(QPointF(100, 0)); handles.push_back(QPointF(100, 0)); setHandles(handles); QSizeF size(100, 100); updatePath(size); } RectangleShape::RectangleShape(const RectangleShape &rhs) - : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), + : KoParameterShape(rhs), m_cornerRadiusX(rhs.m_cornerRadiusX), m_cornerRadiusY(rhs.m_cornerRadiusY) { } RectangleShape::~RectangleShape() { } KoShape *RectangleShape::cloneShape() const { return new RectangleShape(*this); } bool RectangleShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfMandatories | OdfGeometry | OdfAdditionalAttributes | OdfCommonChildElements); if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx", "0")); qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry", "0")); m_cornerRadiusX = rx / (0.5 * size().width()) * 100; m_cornerRadiusY = ry / (0.5 * size().height()) * 100; } else { QString cornerRadius = element.attributeNS(KoXmlNS::draw, "corner-radius", ""); if (!cornerRadius.isEmpty()) { qreal radius = KoUnit::parseValue(cornerRadius); m_cornerRadiusX = qMin(radius / (0.5 * size().width()) * 100, qreal(100)); m_cornerRadiusY = qMin(radius / (0.5 * size().height()) * 100, qreal(100)); } } updatePath(size()); updateHandles(); loadOdfAttributes(element, context, OdfTransformation); loadText(element, context); return true; } void RectangleShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:rect"); saveOdfAttributes(context, OdfAllAttributes); if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) { context.xmlWriter().addAttribute("svg:rx", m_cornerRadiusX * (0.5 * size().width()) / 100.0); context.xmlWriter().addAttribute("svg:ry", m_cornerRadiusY * (0.5 * size().height()) / 100.0); } saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } else { KoPathShape::saveOdf(context); } } void RectangleShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); qreal width2 = size().width() / 2.0; qreal height2 = size().height() / 2.0; switch (handleId) { case 0: if (p.x() < width2) { p.setX(width2); } else if (p.x() > size().width()) { p.setX(size().width()); } p.setY(0); m_cornerRadiusX = (size().width() - p.x()) / width2 * 100.0; if (!(modifiers & Qt::ControlModifier)) { m_cornerRadiusY = (size().width() - p.x()) / height2 * 100.0; } break; case 1: if (p.y() < 0) { p.setY(0); } else if (p.y() > height2) { p.setY(height2); } p.setX(size().width()); m_cornerRadiusY = p.y() / height2 * 100.0; if (!(modifiers & Qt::ControlModifier)) { m_cornerRadiusX = p.y() / width2 * 100.0; } break; } // this is needed otherwise undo/redo might not end in the same result if (100 - m_cornerRadiusX < 1e-10) { m_cornerRadiusX = 100; } if (100 - m_cornerRadiusY < 1e-10) { m_cornerRadiusY = 100; } updateHandles(); } void RectangleShape::updateHandles() { QList handles; handles.append(QPointF(size().width() - m_cornerRadiusX / 100.0 * 0.5 * size().width(), 0.0)); handles.append(QPointF(size().width(), m_cornerRadiusY / 100.0 * 0.5 * size().height())); setHandles(handles); } void RectangleShape::updatePath(const QSizeF &size) { - Q_D(KoParameterShape); - qreal rx = 0; qreal ry = 0; if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) { rx = size.width() / 200.0 * m_cornerRadiusX; ry = size.height() / 200.0 * m_cornerRadiusY; } qreal x2 = size.width() - rx; qreal y2 = size.height() - ry; QPointF curvePoints[12]; int requiredCurvePointCount = 4; if (rx && m_cornerRadiusX < 100) { requiredCurvePointCount += 2; } if (ry && m_cornerRadiusY < 100) { requiredCurvePointCount += 2; } createPoints(requiredCurvePointCount); - KoSubpath &points = *d->subpaths[0]; + KoSubpath &points = *subpaths()[0]; int cp = 0; // first path starts and closes path points[cp]->setProperty(KoPathPoint::StartSubpath); points[cp]->setProperty(KoPathPoint::CloseSubpath); points[cp]->setPoint(QPointF(rx, 0)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) { // end point of the top edge points[++cp]->setPoint(QPointF(x2, 0)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the top right radius arcToCurve(rx, ry, 90, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if (m_cornerRadiusY < 100 || m_cornerRadiusX == 0) { // the right edge points[++cp]->setPoint(QPointF(size.width(), y2)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the bottom right radius arcToCurve(rx, ry, 0, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) { // the bottom edge points[++cp]->setPoint(QPointF(rx, size.height())); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the bottom left radius arcToCurve(rx, ry, 270, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if ((m_cornerRadiusY < 100 || m_cornerRadiusX == 0) && ry) { // the right edge points[++cp]->setPoint(QPointF(0, ry)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the top left radius arcToCurve(rx, ry, 180, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[0]->setControlPoint1(curvePoints[1]); points[0]->setPoint(curvePoints[2]); } // unset all stop/close path properties for (int i = 1; i < cp; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } // last point stops and closes path points.last()->setProperty(KoPathPoint::StopSubpath); points.last()->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } void RectangleShape::createPoints(int requiredPointCount) { - Q_D(KoParameterShape); - - if (d->subpaths.count() != 1) { + if (subpaths().count() != 1) { clear(); - d->subpaths.append(new KoSubpath()); + subpaths().append(new KoSubpath()); } - int currentPointCount = d->subpaths[0]->count(); + int currentPointCount = subpaths()[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { - delete d->subpaths[0]->front(); - d->subpaths[0]->pop_front(); + delete subpaths()[0]->front(); + subpaths()[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { - d->subpaths[0]->append(new KoPathPoint(this, QPointF())); + subpaths()[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } qreal RectangleShape::cornerRadiusX() const { return m_cornerRadiusX; } void RectangleShape::setCornerRadiusX(qreal radius) { radius = qBound(0.0, radius, 100.0); m_cornerRadiusX = radius; updatePath(size()); updateHandles(); } qreal RectangleShape::cornerRadiusY() const { return m_cornerRadiusY; } void RectangleShape::setCornerRadiusY(qreal radius) { radius = qBound(0.0, radius, 100.0); m_cornerRadiusY = radius; updatePath(size()); updateHandles(); } QString RectangleShape::pathShapeId() const { return RectangleShapeId; } bool RectangleShape::saveSvg(SvgSavingContext &context) { // let basic path saiving code handle our saving if (!isParametricShape()) return false; context.shapeWriter().startElement("rect"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); const QSizeF size = this->size(); context.shapeWriter().addAttribute("width", size.width()); context.shapeWriter().addAttribute("height", size.height()); double rx = cornerRadiusX(); if (rx > 0.0) { context.shapeWriter().addAttribute("rx", 0.01 * rx * 0.5 * size.width()); } double ry = cornerRadiusY(); if (ry > 0.0) { context.shapeWriter().addAttribute("ry", 0.01 * ry * 0.5 * size.height()); } context.shapeWriter().endElement(); return true; } bool RectangleShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x")); const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y")); const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width")); const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height")); const QString rxStr = element.attribute("rx"); const QString ryStr = element.attribute("ry"); qreal rx = rxStr.isEmpty() ? 0.0 : SvgUtil::parseUnitX(context.currentGC(), rxStr); qreal ry = ryStr.isEmpty() ? 0.0 : SvgUtil::parseUnitY(context.currentGC(), ryStr); // if one radius is given but not the other, use the same value for both if (!rxStr.isEmpty() && ryStr.isEmpty()) { ry = rx; } if (rxStr.isEmpty() && !ryStr.isEmpty()) { rx = ry; } setSize(QSizeF(w, h)); setPosition(QPointF(x, y)); if (rx >= 0.0) { setCornerRadiusX(qMin(qreal(100.0), qreal(rx / (0.5 * w) * 100.0))); } if (ry >= 0.0) { setCornerRadiusY(qMin(qreal(100.0), qreal(ry / (0.5 * h) * 100.0))); } if (w == 0.0 || h == 0.0) { setVisible(false); } return true; } diff --git a/plugins/flake/pathshapes/spiral/SpiralShape.cpp b/plugins/flake/pathshapes/spiral/SpiralShape.cpp index d20ac46bfe..a6b11ba06b 100644 --- a/plugins/flake/pathshapes/spiral/SpiralShape.cpp +++ b/plugins/flake/pathshapes/spiral/SpiralShape.cpp @@ -1,319 +1,317 @@ /* This file is part of the KDE project Copyright (C) 2007 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 "SpiralShape.h" #include #include #include #include #include #include #include #include "kis_assert.h" SpiralShape::SpiralShape() : m_fade(.9) , m_kindAngle(M_PI) , m_radii(100.0, 100.0) , m_type(Curve) , m_clockwise(true) { //m_handles.push_back(QPointF(50, 0)); //m_handles.push_back(QPointF(50, 50)); //m_handles.push_back(QPointF(0, 50)); createPath(QSizeF(m_radii.x(), m_radii.y())); } SpiralShape::SpiralShape(const SpiralShape &rhs) - : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), + : KoParameterShape(rhs), m_fade(rhs.m_fade), m_kindAngle(rhs.m_kindAngle), m_center(rhs.m_center), m_radii(rhs.m_radii), m_type(rhs.m_type), m_clockwise(rhs.m_clockwise) { Q_FOREACH(KoPathPoint *point, rhs.m_points) { KIS_ASSERT_RECOVER(point) { continue; } m_points << new KoPathPoint(*point, this); } } SpiralShape::~SpiralShape() { } KoShape *SpiralShape::cloneShape() const { return new SpiralShape(*this); } void SpiralShape::saveOdf(KoShapeSavingContext &context) const { // TODO? KoPathShape::saveOdf(context); } bool SpiralShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &/*context*/) { Q_UNUSED(element); // TODO? return true; } void SpiralShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_center = matrix.map(m_center); m_radii = matrix.map(m_radii); KoParameterShape::setSize(newSize); } QPointF SpiralShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); m_center = matrix.map(m_center); return offset; } void SpiralShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(handleId); Q_UNUSED(point); Q_UNUSED(modifiers); #if 0 QPointF p(point); QPointF diff(m_center - point); diff.setX(-diff.x()); qreal angle = 0; if (diff.x() == 0) { angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0; } else { diff.setY(diff.y() * m_radii.x() / m_radii.y()); angle = atan(diff.y() / diff.x()); if (angle < 0) { angle = M_PI + angle; } if (diff.y() < 0) { angle += M_PI; } } switch (handleId) { case 0: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_handles[handleId] = p; updateKindHandle(); break; case 1: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_handles[handleId] = p; updateKindHandle(); break; case 2: { QList kindHandlePositions; kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()))); kindHandlePositions.push_back(m_center); kindHandlePositions.push_back((m_handles[0] + m_handles[1]) / 2.0); QPointF diff = m_center * 2.0; int handlePos = 0; for (int i = 0; i < kindHandlePositions.size(); ++i) { QPointF pointDiff(p - kindHandlePositions[i]); if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) { diff = pointDiff; handlePos = i; } } m_handles[handleId] = kindHandlePositions[handlePos]; m_type = SpiralType(handlePos); } break; } #endif } void SpiralShape::updatePath(const QSizeF &size) { createPath(size); normalize(); #if 0 Q_UNUSED(size); QPointF startpoint(m_handles[0]); QPointF curvePoints[12]; int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, sweepAngle(), startpoint, curvePoints); int cp = 0; m_points[cp]->setPoint(startpoint); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint1); for (int i = 0; i < pointCnt; i += 3) { m_points[cp]->setControlPoint2(curvePoints[i]); m_points[++cp]->setControlPoint1(curvePoints[i + 1]); m_points[cp]->setPoint(curvePoints[i + 2]); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint2); } if (m_type == Curve) { m_points[++cp]->setPoint(m_center); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint1); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint2); } else if (m_type == Line && m_startAngle == m_endAngle) { m_points[0]->setControlPoint1(m_points[cp]->controlPoint1()); m_points[0]->setPoint(m_points[cp]->point()); --cp; } d->m_subpaths[0]->clear(); for (int i = 0; i <= cp; ++i) { if (i < cp || (m_type == Line && m_startAngle != m_endAngle)) { m_points[i]->unsetProperty(KoPathPoint::CloseSubpath); } else { m_points[i]->setProperty(KoPathPoint::CloseSubpath); } d->m_subpaths[0]->push_back(m_points[i]); } #endif } void SpiralShape::createPath(const QSizeF &size) { - Q_D(KoParameterShape); - Q_UNUSED(size); clear(); QPointF center = QPointF(m_radii.x() / 2.0, m_radii.y() / 2.0); //moveTo(QPointF(size.width(), m_radii.y())); qreal adv_ang = (m_clockwise ? -1.0 : 1.0) * M_PI_2; // radius of first segment is non-faded radius: qreal m_radius = m_radii.x() / 2.0; qreal r = m_radius; QPointF oldP(center.x(), (m_clockwise ? -1.0 : 1.0) * m_radius + center.y()); QPointF newP; QPointF newCenter(center); moveTo(oldP); uint m_segments = 10; //m_handles[0] = oldP; for (uint i = 0; i < m_segments; ++i) { newP.setX(r * cos(adv_ang * (i + 2)) + newCenter.x()); newP.setY(r * sin(adv_ang * (i + 2)) + newCenter.y()); if (m_type == Curve) { qreal rx = qAbs(oldP.x() - newP.x()); qreal ry = qAbs(oldP.y() - newP.y()); if (m_clockwise) { arcTo(rx, ry, ((i + 1) % 4) * 90, 90); } else { arcTo(rx, ry, 360 - ((i + 1) % 4) * 90, -90); } } else { lineTo(newP); } newCenter += (newP - newCenter) * (1.0 - m_fade); oldP = newP; r *= m_fade; } //m_handles[1] = QPointF(center.x(), (m_clockwise ? -1.0 : 1.0) * m_radius + center.y()); - m_points = *d->subpaths[0]; + m_points = *subpaths()[0]; notifyPointsChanged(); } void SpiralShape::updateKindHandle() { /* m_kindAngle = (m_startAngle + m_endAngle) * M_PI / 360.0; if (m_startAngle > m_endAngle) { m_kindAngle += M_PI; } switch (m_type) { case Curve: m_handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()); break; case Line: m_handles[2] = m_center; break; } */ } void SpiralShape::updateAngleHandles() { // qreal startRadian = m_startAngle * M_PI / 180.0; // qreal endRadian = m_endAngle * M_PI / 180.0; // m_handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y()); // m_handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y()); } void SpiralShape::setType(SpiralType type) { m_type = type; updateKindHandle(); updatePath(size()); } SpiralShape::SpiralType SpiralShape::type() const { return m_type; } void SpiralShape::setFade(qreal fade) { m_fade = fade; updateKindHandle(); //updateAngleHandles(); updatePath(size()); } qreal SpiralShape::fade() const { return m_fade; } bool SpiralShape::clockWise() const { return m_clockwise; } void SpiralShape::setClockWise(bool clockWise) { m_clockwise = clockWise; updateKindHandle(); //updateAngleHandles(); updatePath(size()); } QString SpiralShape::pathShapeId() const { return SpiralShapeId; } diff --git a/plugins/flake/pathshapes/star/StarShape.cpp b/plugins/flake/pathshapes/star/StarShape.cpp index 99c7ab1e99..631b82acd8 100644 --- a/plugins/flake/pathshapes/star/StarShape.cpp +++ b/plugins/flake/pathshapes/star/StarShape.cpp @@ -1,459 +1,453 @@ /* This file is part of the KDE project Copyright (C) 2006-2009 Jan Hambrecht Copyright (C) 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "StarShape.h" #include #include #include #include #include #include #include #include #include StarShape::StarShape() : m_cornerCount(5) , m_zoomX(1.0) , m_zoomY(1.0) , m_convex(false) { m_radius[base] = 25.0; m_radius[tip] = 50.0; m_angles[base] = m_angles[tip] = defaultAngleRadian(); m_roundness[base] = m_roundness[tip] = 0.0f; m_center = QPointF(50, 50); updatePath(QSize(100, 100)); } StarShape::StarShape(const StarShape &rhs) - : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), + : KoParameterShape(rhs), m_cornerCount(rhs.m_cornerCount), m_radius(rhs.m_radius), m_angles(rhs.m_angles), m_zoomX(rhs.m_zoomX), m_zoomY(rhs.m_zoomY), m_roundness(rhs.m_roundness), m_center(rhs.m_center), m_convex(rhs.m_convex) { } StarShape::~StarShape() { } KoShape *StarShape::cloneShape() const { return new StarShape(*this); } void StarShape::setCornerCount(uint cornerCount) { if (cornerCount >= 3) { double oldDefaultAngle = defaultAngleRadian(); m_cornerCount = cornerCount; double newDefaultAngle = defaultAngleRadian(); m_angles[base] += newDefaultAngle - oldDefaultAngle; m_angles[tip] += newDefaultAngle - oldDefaultAngle; updatePath(QSize()); } } uint StarShape::cornerCount() const { return m_cornerCount; } void StarShape::setBaseRadius(qreal baseRadius) { m_radius[base] = fabs(baseRadius); updatePath(QSize()); } qreal StarShape::baseRadius() const { return m_radius[base]; } void StarShape::setTipRadius(qreal tipRadius) { m_radius[tip] = fabs(tipRadius); updatePath(QSize()); } qreal StarShape::tipRadius() const { return m_radius[tip]; } void StarShape::setBaseRoundness(qreal baseRoundness) { m_roundness[base] = baseRoundness; updatePath(QSize()); } void StarShape::setTipRoundness(qreal tipRoundness) { m_roundness[tip] = tipRoundness; updatePath(QSize()); } void StarShape::setConvex(bool convex) { m_convex = convex; updatePath(QSize()); } bool StarShape::convex() const { return m_convex; } QPointF StarShape::starCenter() const { return m_center; } void StarShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { if (modifiers & Qt::ShiftModifier) { QPointF handle = handles()[handleId]; QPointF tangentVector = point - handle; qreal distance = sqrt(tangentVector.x() * tangentVector.x() + tangentVector.y() * tangentVector.y()); QPointF radialVector = handle - m_center; // cross product to determine in which direction the user is dragging qreal moveDirection = radialVector.x() * tangentVector.y() - radialVector.y() * tangentVector.x(); // make the roundness stick to zero if distance is under a certain value float snapDistance = 3.0; if (distance >= 0.0) { distance = distance < snapDistance ? 0.0 : distance - snapDistance; } else { distance = distance > -snapDistance ? 0.0 : distance + snapDistance; } // control changes roundness on both handles, else only the actual handle roundness is changed if (modifiers & Qt::ControlModifier) { m_roundness[handleId] = moveDirection < 0.0f ? distance : -distance; } else { m_roundness[base] = m_roundness[tip] = moveDirection < 0.0f ? distance : -distance; } } else { QPointF distVector = point - m_center; // unapply scaling distVector.rx() /= m_zoomX; distVector.ry() /= m_zoomY; m_radius[handleId] = sqrt(distVector.x() * distVector.x() + distVector.y() * distVector.y()); qreal angle = atan2(distVector.y(), distVector.x()); if (angle < 0.0) { angle += 2.0 * M_PI; } qreal diffAngle = angle - m_angles[handleId]; qreal radianStep = M_PI / static_cast(m_cornerCount); if (handleId == tip) { m_angles[tip] += diffAngle - radianStep; m_angles[base] += diffAngle - radianStep; } else { // control make the base point move freely if (modifiers & Qt::ControlModifier) { m_angles[base] += diffAngle - 2 * radianStep; } else { m_angles[base] = m_angles[tip]; } } } } void StarShape::updatePath(const QSizeF &size) { - Q_D(KoParameterShape); - Q_UNUSED(size); qreal radianStep = M_PI / static_cast(m_cornerCount); createPoints(m_convex ? m_cornerCount : 2 * m_cornerCount); - KoSubpath &points = *d->subpaths[0]; + KoSubpath &points = *subpaths()[0]; uint index = 0; for (uint i = 0; i < 2 * m_cornerCount; ++i) { uint cornerType = i % 2; if (cornerType == base && m_convex) { continue; } qreal radian = static_cast((i + 1) * radianStep) + m_angles[cornerType]; QPointF cornerPoint = QPointF(m_zoomX * m_radius[cornerType] * cos(radian), m_zoomY * m_radius[cornerType] * sin(radian)); points[index]->setPoint(m_center + cornerPoint); points[index]->unsetProperty(KoPathPoint::StopSubpath); points[index]->unsetProperty(KoPathPoint::CloseSubpath); if (m_roundness[cornerType] > 1e-10 || m_roundness[cornerType] < -1e-10) { // normalized cross product to compute tangential vector for handle point QPointF tangentVector(cornerPoint.y() / m_radius[cornerType], -cornerPoint.x() / m_radius[cornerType]); points[index]->setControlPoint2(points[index]->point() - m_roundness[cornerType] * tangentVector); points[index]->setControlPoint1(points[index]->point() + m_roundness[cornerType] * tangentVector); } else { points[index]->removeControlPoint1(); points[index]->removeControlPoint2(); } index++; } // first path starts and closes path points[0]->setProperty(KoPathPoint::StartSubpath); points[0]->setProperty(KoPathPoint::CloseSubpath); // last point stops and closes path points.last()->setProperty(KoPathPoint::StopSubpath); points.last()->setProperty(KoPathPoint::CloseSubpath); normalize(); QList handles; handles.push_back(points.at(tip)->point()); if (!m_convex) { handles.push_back(points.at(base)->point()); } setHandles(handles); m_center = computeCenter(); } void StarShape::createPoints(int requiredPointCount) { - Q_D(KoParameterShape); - - if (d->subpaths.count() != 1) { + if (subpaths().count() != 1) { clear(); - d->subpaths.append(new KoSubpath()); + subpaths().append(new KoSubpath()); } - int currentPointCount = d->subpaths[0]->count(); + int currentPointCount = subpaths()[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { - delete d->subpaths[0]->front(); - d->subpaths[0]->pop_front(); + delete subpaths()[0]->front(); + subpaths()[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { - d->subpaths[0]->append(new KoPathPoint(this, QPointF())); + subpaths()[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } void StarShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_zoomX *= matrix.m11(); m_zoomY *= matrix.m22(); // this transforms the handles KoParameterShape::setSize(newSize); m_center = computeCenter(); } QPointF StarShape::computeCenter() const { - Q_D(const KoParameterShape); - - KoSubpath &points = *d->subpaths[0]; + KoSubpath &points = *subpaths()[0]; QPointF center(0, 0); for (uint i = 0; i < m_cornerCount; ++i) { if (m_convex) { center += points[i]->point(); } else { center += points[2 * i]->point(); } } return center / static_cast(m_cornerCount); } bool StarShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { bool loadAsCustomShape = false; if (element.localName() == "custom-shape") { QString drawEngine = element.attributeNS(KoXmlNS::draw, "engine", ""); if (drawEngine != "calligra:star") { return false; } loadAsCustomShape = true; } else if (element.localName() != "regular-polygon") { return false; } m_radius[tip] = 50; m_center = QPointF(50, 50); if (!loadAsCustomShape) { QString corners = element.attributeNS(KoXmlNS::draw, "corners", ""); if (!corners.isEmpty()) { m_cornerCount = corners.toUInt(); // initialize default angles of tip and base m_angles[base] = m_angles[tip] = defaultAngleRadian(); } m_convex = (element.attributeNS(KoXmlNS::draw, "concave", "false") == "false"); if (m_convex) { m_radius[base] = m_radius[tip]; } else { // sharpness is radius of ellipse on which inner polygon points are located // 0% means all polygon points are on a single ellipse // 100% means inner points are located at polygon center point QString sharpness = element.attributeNS(KoXmlNS::draw, "sharpness", ""); if (!sharpness.isEmpty() && sharpness.right(1) == "%") { float percent = sharpness.left(sharpness.length() - 1).toFloat(); m_radius[base] = m_radius[tip] * (100 - percent) / 100; } } } else { QString drawData = element.attributeNS(KoXmlNS::draw, "data"); if (drawData.isEmpty()) { return false; } QStringList properties = drawData.split(';'); if (properties.count() == 0) { return false; } foreach (const QString &property, properties) { QStringList pair = property.split(':'); if (pair.count() != 2) { continue; } if (pair[0] == "corners") { m_cornerCount = pair[1].toInt(); } else if (pair[0] == "concave") { m_convex = (pair[1] == "false"); } else if (pair[0] == "baseRoundness") { m_roundness[base] = pair[1].toDouble(); } else if (pair[0] == "tipRoundness") { m_roundness[tip] = pair[1].toDouble(); } else if (pair[0] == "baseAngle") { m_angles[base] = pair[1].toDouble(); } else if (pair[0] == "tipAngle") { m_angles[tip] = pair[1].toDouble(); } else if (pair[0] == "sharpness") { float percent = pair[1].left(pair[1].length() - 1).toFloat(); m_radius[base] = m_radius[tip] * (100 - percent) / 100; } } if (m_convex) { m_radius[base] = m_radius[tip]; } } updatePath(QSizeF()); // reset transformation setTransformation(QTransform()); loadOdfAttributes(element, context, OdfAllAttributes); loadText(element, context); return true; } void StarShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { double defaultAngle = defaultAngleRadian(); bool hasRoundness = m_roundness[tip] != 0.0f || m_roundness[base] != 0.0f; bool hasAngleOffset = m_angles[base] != defaultAngle || m_angles[tip] != defaultAngle; if (hasRoundness || hasAngleOffset) { // draw:regular-polygon has no means of saving roundness // so we save as a custom shape with a specific draw:engine context.xmlWriter().startElement("draw:custom-shape"); saveOdfAttributes(context, OdfAllAttributes); // now write the special shape data context.xmlWriter().addAttribute("draw:engine", "calligra:star"); // create the data attribute QString drawData = QString("corners:%1;").arg(m_cornerCount); drawData += m_convex ? "concave:false;" : "concave:true;"; if (!m_convex) { // sharpness is radius of ellipse on which inner polygon points are located // 0% means all polygon points are on a single ellipse // 100% means inner points are located at polygon center point qreal percent = (m_radius[tip] - m_radius[base]) / m_radius[tip] * 100.0; drawData += QString("sharpness:%1%;").arg(percent); } if (m_roundness[base] != 0.0f) { drawData += QString("baseRoundness:%1;").arg(m_roundness[base]); } if (m_roundness[tip] != 0.0f) { drawData += QString("tipRoundness:%1;").arg(m_roundness[tip]); } drawData += QString("baseAngle:%1;").arg(m_angles[base]); drawData += QString("tipAngle:%1;").arg(m_angles[tip]); context.xmlWriter().addAttribute("draw:data", drawData); saveOdfCommonChildElements(context); saveText(context); // write a enhanced geometry element for compatibility with other applications context.xmlWriter().startElement("draw:enhanced-geometry"); context.xmlWriter().addAttribute("draw:enhanced-path", toString(transformation())); context.xmlWriter().endElement(); // draw:enhanced-geometry context.xmlWriter().endElement(); // draw:custom-shape } else { context.xmlWriter().startElement("draw:regular-polygon"); saveOdfAttributes(context, OdfAllAttributes); context.xmlWriter().addAttribute("draw:corners", m_cornerCount); context.xmlWriter().addAttribute("draw:concave", m_convex ? "false" : "true"); if (!m_convex) { // sharpness is radius of ellipse on which inner polygon points are located // 0% means all polygon points are on a single ellipse // 100% means inner points are located at polygon center point qreal percent = (m_radius[tip] - m_radius[base]) / m_radius[tip] * 100.0; context.xmlWriter().addAttribute("draw:sharpness", QString("%1%").arg(percent)); } saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } } else { KoPathShape::saveOdf(context); } } QString StarShape::pathShapeId() const { return StarShapeId; } double StarShape::defaultAngleRadian() const { qreal radianStep = M_PI / static_cast(m_cornerCount); return M_PI_2 - 2 * radianStep; } diff --git a/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp b/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp index ed8645bf80..a2b0c4a838 100644 --- a/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp +++ b/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp @@ -1,193 +1,191 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 Sebastian Sauer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ShrinkToFitShapeContainer.h" #include #include ShrinkToFitShapeContainer::ShrinkToFitShapeContainer(KoShape *childShape, KoDocumentResourceManager *documentResources) - : KoShapeContainer(new ShrinkToFitShapeContainerPrivate(this, childShape)) + : KoShapeContainer() + , d(new Private(childShape)) { Q_UNUSED(documentResources); - Q_D(ShrinkToFitShapeContainer); setPosition(childShape->position()); setSize(childShape->size()); setZIndex(childShape->zIndex()); setRunThrough(childShape->runThrough()); rotate(childShape->rotation()); //setTransformation(childShape->transformation()); if (childShape->parent()) { childShape->parent()->addShape(this); childShape->setParent(0); } childShape->setPosition(QPointF(0.0, 0.0)); // since its relative to my position, this won't move it childShape->setSelectable(false); // our ShrinkToFitShapeContainer will handle that from now on - d->model = new ShrinkToFitShapeContainerModel(this, d); + setModel(new ShrinkToFitShapeContainerModel(this)); addShape(childShape); QSet delegates; delegates << childShape; setToolDelegates(delegates); KoTextShapeData *data = dynamic_cast(childShape->userData()); Q_ASSERT(data); KoTextDocumentLayout *lay = qobject_cast(data->document()->documentLayout()); Q_ASSERT(lay); - QObject::connect(lay, SIGNAL(finishedLayout()), static_cast(d->model), SLOT(finishedLayout())); + QObject::connect(lay, SIGNAL(finishedLayout()), static_cast(model()), SLOT(finishedLayout())); } ShrinkToFitShapeContainer::~ShrinkToFitShapeContainer() { } void ShrinkToFitShapeContainer::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); //painter.fillRect(converter.documentToView(QRectF(QPointF(0,0),size())), QBrush(QColor("#ffcccc"))); // for testing } bool ShrinkToFitShapeContainer::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(element); Q_UNUSED(context); return true; } void ShrinkToFitShapeContainer::saveOdf(KoShapeSavingContext &context) const { - Q_D(const ShrinkToFitShapeContainer); d->childShape->saveOdf(context); } ShrinkToFitShapeContainer *ShrinkToFitShapeContainer::wrapShape(KoShape *shape, KoDocumentResourceManager *documentResourceManager) { Q_ASSERT(dynamic_cast(shape->userData())); Q_ASSERT(qobject_cast(dynamic_cast(shape->userData())->document()->documentLayout())); return new ShrinkToFitShapeContainer(shape, documentResourceManager); } void ShrinkToFitShapeContainer::tryWrapShape(KoShape *shape, const KoXmlElement &element, KoShapeLoadingContext &context) { KoTextShapeData *data = dynamic_cast(shape->userData()); if (!data || data->resizeMethod() != KoTextShapeData::ShrinkToFitResize) { return; } KoShapeContainer *oldParent = shape->parent(); ShrinkToFitShapeContainer *tos = wrapShape(shape, context.documentResourceManager()); if (!tos->loadOdf(element, context)) { shape->setParent(oldParent); delete tos; } } void ShrinkToFitShapeContainer::unwrapShape(KoShape *shape) { Q_ASSERT(shape->parent() == this); removeShape(shape); shape->setParent(parent()); QSet delegates = toolDelegates(); delegates.remove(shape); setToolDelegates(delegates); shape->setPosition(position()); shape->setSize(size()); shape->rotate(rotation()); shape->setSelectable(true); } -ShrinkToFitShapeContainerModel::ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q, ShrinkToFitShapeContainerPrivate *d) +ShrinkToFitShapeContainerModel::ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q) : q(q) - , d(d) , m_scale(1.0) , m_dirty(10) , m_maybeUpdate(false) { } void ShrinkToFitShapeContainerModel::finishedLayout() { m_maybeUpdate = true; containerChanged(q, KoShape::SizeChanged); m_maybeUpdate = false; } void ShrinkToFitShapeContainerModel::containerChanged(KoShapeContainer *container, KoShape::ChangeType type) { Q_ASSERT(container == q); Q_UNUSED(container); if (type == KoShape::SizeChanged) { - KoTextShapeData *data = dynamic_cast(d->childShape->userData()); + KoTextShapeData *data = dynamic_cast(q->d->childShape->userData()); Q_ASSERT(data); KoTextLayoutRootArea *rootArea = data->rootArea(); Q_ASSERT(rootArea); QSizeF shapeSize = q->size(); QSizeF documentSize = rootArea->boundingRect().size(); if (m_maybeUpdate && shapeSize == m_shapeSize && documentSize == m_documentSize) { m_dirty = 0; return; // nothing to update } m_shapeSize = shapeSize; m_documentSize = documentSize; if (documentSize.width() > 0.0 && documentSize.height() > 0.0) { if (m_dirty || !m_maybeUpdate) { qreal scaleX = qMin(1.0, shapeSize.width() / documentSize.width()); qreal scaleY = qMin(1.0, shapeSize.height() / documentSize.height()); m_scale = (scaleX + scaleY) / 2.0 * 0.95; if (m_maybeUpdate && m_dirty) { --m_dirty; } } } else { m_scale = 1.0; m_dirty = 1; } QSizeF newSize(shapeSize.width() / m_scale, shapeSize.height() / m_scale); - d->childShape->setSize(newSize); + q->d->childShape->setSize(newSize); QTransform m; m.scale(m_scale, m_scale); - d->childShape->setTransformation(m); + q->d->childShape->setTransformation(m); } } bool ShrinkToFitShapeContainerModel::inheritsTransform(const KoShape *child) const { - Q_ASSERT(child == d->childShape); Q_UNUSED(child); + Q_ASSERT(child == q->d->childShape); Q_UNUSED(child); return true; } bool ShrinkToFitShapeContainerModel::isClipped(const KoShape *child) const { - Q_ASSERT(child == d->childShape); Q_UNUSED(child); + Q_ASSERT(child == q->d->childShape); Q_UNUSED(child); return false; } diff --git a/plugins/flake/textshape/ShrinkToFitShapeContainer.h b/plugins/flake/textshape/ShrinkToFitShapeContainer.h index 79d84f10a2..3ca8063f9a 100644 --- a/plugins/flake/textshape/ShrinkToFitShapeContainer.h +++ b/plugins/flake/textshape/ShrinkToFitShapeContainer.h @@ -1,115 +1,116 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 Sebastian Sauer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 SHRINKTOFITSHAPECONTAINER_H #define SHRINKTOFITSHAPECONTAINER_H #include #include #include #include #include #include #include #include #include #include #include #include #include -/** - * \internal d-pointer class for the \a ShrinkToFitShapeContainer class. - */ -class ShrinkToFitShapeContainerPrivate : public KoShapeContainerPrivate -{ -public: - explicit ShrinkToFitShapeContainerPrivate(KoShapeContainer *q, KoShape *childShape) : KoShapeContainerPrivate(q), childShape(childShape) {} - ~ShrinkToFitShapeContainerPrivate() override {} - KoShape *childShape; // the original shape not owned by us -}; - /** * Container that is used to wrap a shape and shrink a text-shape to fit the content. */ class ShrinkToFitShapeContainer : public KoShapeContainer { public: explicit ShrinkToFitShapeContainer(KoShape *childShape, KoDocumentResourceManager *documentResources = 0); ~ShrinkToFitShapeContainer() override; // reimplemented void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override; // reimplemented bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; // reimplemented void saveOdf(KoShapeSavingContext &context) const override; /** * Factory function to create and return a ShrinkToFitShapeContainer instance that wraps the \a shape with it. */ static ShrinkToFitShapeContainer *wrapShape(KoShape *shape, KoDocumentResourceManager *documentResourceManager = 0); /** * Try to load text-on-shape from \a element and wrap \a shape with it. */ static void tryWrapShape(KoShape *shape, const KoXmlElement &element, KoShapeLoadingContext &context); /** * Undo the wrapping done in the \a wrapShape method. */ void unwrapShape(KoShape *shape); private: - Q_DECLARE_PRIVATE(ShrinkToFitShapeContainer) + friend class ShrinkToFitShapeContainerModel; + class Private; + QSharedDataPointer d; +}; + +/** + * \internal d-pointer class for the \a ShrinkToFitShapeContainer class. + */ +class ShrinkToFitShapeContainer::Private : public QSharedData +{ +public: + explicit Private(KoShape *childShape) : childShape(childShape) {} + virtual ~Private() = default; + KoShape *childShape; // the original shape not owned by us }; /** * The container-model class implements \a KoShapeContainerModel for the \a ShrinkToFitShapeContainer to * to stuff once our container changes. */ class ShrinkToFitShapeContainerModel : public QObject, public SimpleShapeContainerModel { Q_OBJECT friend class ShrinkToFitShapeContainer; public: - ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q, ShrinkToFitShapeContainerPrivate *d); + ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q); // reimplemented void containerChanged(KoShapeContainer *container, KoShape::ChangeType type) override; // reimplemented bool inheritsTransform(const KoShape *child) const override; // reimplemented bool isClipped(const KoShape *child) const override; private Q_SLOTS: void finishedLayout(); private: ShrinkToFitShapeContainer *q; - ShrinkToFitShapeContainerPrivate *d; qreal m_scale; QSizeF m_shapeSize, m_documentSize; int m_dirty; bool m_maybeUpdate; }; #endif diff --git a/plugins/flake/textshape/TextShape.cpp b/plugins/flake/textshape/TextShape.cpp index c5539fe4e4..f31b10bf88 100644 --- a/plugins/flake/textshape/TextShape.cpp +++ b/plugins/flake/textshape/TextShape.cpp @@ -1,466 +1,472 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008-2010 Thorsten Zachmann * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2010 KO GmbH * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 "TextShape.h" #include "ShrinkToFitShapeContainer.h" #include #include "SimpleRootAreaProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painting_tweaks.h" TextShape::TextShape(KoInlineTextObjectManager *inlineTextObjectManager, KoTextRangeManager *textRangeManager) : KoShapeContainer(new KoTextShapeContainerModel()) , KoFrameShape(KoXmlNS::draw, "text-box") , m_pageProvider(0) , m_imageCollection(0) , m_clip(true) { setShapeId(TextShape_SHAPEID); m_textShapeData = new KoTextShapeData(); setUserData(m_textShapeData); SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this); KoTextDocument(m_textShapeData->document()).setInlineTextObjectManager(inlineTextObjectManager); KoTextDocument(m_textShapeData->document()).setTextRangeManager(textRangeManager); m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider); m_textShapeData->document()->setDocumentLayout(m_layout); setCollisionDetection(true); QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout())); } TextShape::TextShape(const TextShape &rhs) - : KoShapeContainer(new KoShapeContainerPrivate(*reinterpret_cast(rhs.d_ptr), this)), - KoFrameShape(rhs), - m_textShapeData(dynamic_cast(rhs.m_textShapeData->clone())), - m_pageProvider(0), - m_imageCollection(0), - m_clip(rhs.m_clip) + : KoShapeContainer(rhs) + , KoFrameShape(rhs) + , m_textShapeData(dynamic_cast(rhs.m_textShapeData->clone())) + , m_pageProvider(0) + , m_imageCollection(0) + , m_clip(rhs.m_clip) { - reinterpret_cast(rhs.d_ptr)->model = new KoTextShapeContainerModel(); + /// TODO: we need to clone the model + KoTextShapeContainerModel *origModel = dynamic_cast(rhs.model()); + if (origModel) { + setModel(new KoTextShapeContainerModel()); + } + // XXX: ? + //reinterpret_cast(rhs.d_ptr)->model = new KoTextShapeContainerModel(); setShapeId(TextShape_SHAPEID); setUserData(m_textShapeData); SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this); m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider); m_textShapeData->document()->setDocumentLayout(m_layout); setCollisionDetection(true); QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout())); updateDocumentData(); m_layout->scheduleLayout(); } TextShape::~TextShape() { } KoShape *TextShape::cloneShape() const { return new TextShape(*this); } void TextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { painter.save(); applyConversion(painter, converter); KoBorder *border = this->border(); if (border) { paintBorder(painter, converter); } else if (paintContext.showTextShapeOutlines) { // No need to paint the outlines if there is a real border. if (qAbs(rotation()) > 1) { painter.setRenderHint(QPainter::Antialiasing); } QPen pen(QColor(210, 210, 210)); // use cosmetic pen QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0)); QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y())); painter.setPen(pen); painter.drawRect(rect); } painter.restore(); if (m_textShapeData->isDirty()) { // not layouted yet. return; } QTextDocument *doc = m_textShapeData->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); Q_ASSERT(lay); lay->showInlineObjectVisualization(paintContext.showInlineObjectVisualization); applyConversion(painter, converter); if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } // this enables to use the same shapes on different pages showing different page numbers if (m_pageProvider) { KoTextPage *page = m_pageProvider->page(this); if (page) { // this is used to not trigger repaints if layout during the painting is done m_paintRegion = KisPaintingTweaks::safeClipRegion(painter); if (!m_textShapeData->rootArea()->page() || page->pageNumber() != m_textShapeData->rootArea()->page()->pageNumber()) { m_textShapeData->rootArea()->setPage(page); // takes over ownership of the page } else { delete page; } } } KoTextDocumentLayout::PaintContext pc; QAbstractTextDocumentLayout::Selection selection; KoTextEditor *textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); selection.cursor = *(textEditor->cursor()); QPalette palette = pc.textContext.palette; selection.format.setBackground(palette.brush(QPalette::Highlight)); selection.format.setForeground(palette.brush(QPalette::HighlightedText)); pc.textContext.selections.append(selection); pc.textContext.selections += KoTextDocument(doc).selections(); pc.viewConverter = &converter; pc.imageCollection = m_imageCollection; pc.showFormattingCharacters = paintContext.showFormattingCharacters; pc.showTableBorders = paintContext.showTableBorders; pc.showSectionBounds = paintContext.showSectionBounds; pc.showSpellChecking = paintContext.showSpellChecking; pc.showSelections = paintContext.showSelections; // When clipping the painter we need to make sure not to cutoff cosmetic pens which // may used to draw e.g. table-borders for user convenience when on screen (but not // on e.g. printing). Such cosmetic pens are special cause they will always have the // same pen-width (1 pixel) independent of zoom-factor or painter transformations and // are not taken into account in any border-calculations. QRectF clipRect = outlineRect(); qreal cosmeticPenX = 1 * 72. / painter.device()->logicalDpiX(); qreal cosmeticPenY = 1 * 72. / painter.device()->logicalDpiY(); painter.setClipRect(clipRect.adjusted(-cosmeticPenX, -cosmeticPenY, cosmeticPenX, cosmeticPenY), Qt::IntersectClip); painter.save(); painter.translate(0, -m_textShapeData->documentOffset()); m_textShapeData->rootArea()->paint(&painter, pc); // only need to draw ourselves painter.restore(); m_paintRegion = QRegion(); } QPointF TextShape::convertScreenPos(const QPointF &point) const { QPointF p = absoluteTransformation(0).inverted().map(point); return p + QPointF(0.0, m_textShapeData->documentOffset()); } QPainterPath TextShape::outline() const { QPainterPath path; path.addRect(QRectF(QPointF(0, 0), size())); return path; } QRectF TextShape::outlineRect() const { if (m_textShapeData->rootArea()) { QRectF rect = m_textShapeData->rootArea()->boundingRect(); rect.moveTop(rect.top() - m_textShapeData->rootArea()->top()); if (m_clip) { rect.setHeight(size().height()); } return rect | QRectF(QPointF(0, 0), size()); } return QRectF(QPointF(0, 0), size()); } void TextShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); if (type == PositionChanged || type == SizeChanged || type == CollisionDetected) { m_textShapeData->setDirty(); } } void TextShape::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &writer = context.xmlWriter(); QString textHeight = additionalAttribute("fo:min-height"); const_cast(this)->removeAdditionalAttribute("fo:min-height"); writer.startElement("draw:frame"); // if the TextShape is wrapped in a shrink to fit container we need to save the geometry of the container as // the geometry of the shape might have been changed. if (ShrinkToFitShapeContainer *stf = dynamic_cast(this->parent())) { stf->saveOdfAttributes(context, OdfSize | OdfPosition | OdfTransformation); saveOdfAttributes(context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements); } else { saveOdfAttributes(context, OdfAllAttributes); } writer.startElement("draw:text-box"); if (!textHeight.isEmpty()) { writer.addAttribute("fo:min-height", textHeight); } KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); int index = -1; if (lay) { int i = 0; foreach (KoShape *shape, lay->shapes()) { if (shape == this) { index = i; } else if (index >= 0) { writer.addAttribute("draw:chain-next-name", shape->name()); break; } ++i; } } const bool saveMyText = index == 0; // only save the text once. m_textShapeData->saveOdf(context, 0, 0, saveMyText ? -1 : 0); writer.endElement(); // draw:text-box saveOdfCommonChildElements(context); writer.endElement(); // draw:frame } QString TextShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Qt::Alignment vAlign(m_textShapeData->verticalAlignment()); QString verticalAlign = "top"; if (vAlign == Qt::AlignBottom) { verticalAlign = "bottom"; } else if (vAlign == Qt::AlignVCenter) { verticalAlign = "middle"; } style.addProperty("draw:textarea-vertical-align", verticalAlign); KoTextShapeData::ResizeMethod resize = m_textShapeData->resizeMethod(); if (resize == KoTextShapeData::AutoGrowWidth || resize == KoTextShapeData::AutoGrowWidthAndHeight) { style.addProperty("draw:auto-grow-width", "true"); } if (resize != KoTextShapeData::AutoGrowHeight && resize != KoTextShapeData::AutoGrowWidthAndHeight) { style.addProperty("draw:auto-grow-height", "false"); } if (resize == KoTextShapeData::ShrinkToFitResize) { style.addProperty("draw:fit-to-size", "true"); } m_textShapeData->saveStyle(style, context); return KoShape::saveStyle(style, context); } void TextShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { KoShape::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align")); Qt::Alignment alignment(Qt::AlignTop); if (verticalAlign == "bottom") { alignment = Qt::AlignBottom; } else if (verticalAlign == "justify") { // not yet supported alignment = Qt::AlignVCenter; } else if (verticalAlign == "middle") { alignment = Qt::AlignVCenter; } m_textShapeData->setVerticalAlignment(alignment); const QString fitToSize = styleStack.property(KoXmlNS::draw, "fit-to-size"); KoTextShapeData::ResizeMethod resize = KoTextShapeData::NoResize; if (fitToSize == "true" || fitToSize == "shrink-to-fit") { // second is buggy value from impress resize = KoTextShapeData::ShrinkToFitResize; } else { // An explicit svg:width or svg:height defined do change the default value (means those value // used if not explicit defined otherwise) for auto-grow-height and auto-grow-height. So // they are mutable exclusive. // It is not clear (means we did not test and took care of it) what happens if both are // defined and are in conflict with each other or how the fit-to-size is related to this. QString autoGrowWidth = styleStack.property(KoXmlNS::draw, "auto-grow-width"); if (autoGrowWidth.isEmpty()) { autoGrowWidth = element.hasAttributeNS(KoXmlNS::svg, "width") ? "false" : "true"; } QString autoGrowHeight = styleStack.property(KoXmlNS::draw, "auto-grow-height"); if (autoGrowHeight.isEmpty()) { autoGrowHeight = element.hasAttributeNS(KoXmlNS::svg, "height") ? "false" : "true"; } if (autoGrowWidth == "true") { resize = autoGrowHeight == "true" ? KoTextShapeData::AutoGrowWidthAndHeight : KoTextShapeData::AutoGrowWidth; } else if (autoGrowHeight == "true") { resize = KoTextShapeData::AutoGrowHeight; } } m_textShapeData->setResizeMethod(resize); } bool TextShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { m_textShapeData->document()->setUndoRedoEnabled(false); loadOdfAttributes(element, context, OdfAllAttributes); // this cannot be done in loadStyle as that fills the style stack incorrectly and therefore // it results in wrong data being loaded. m_textShapeData->loadStyle(element, context); #ifndef NWORKAROUND_ODF_BUGS KoTextShapeData::ResizeMethod method = m_textShapeData->resizeMethod(); if (KoOdfWorkaround::fixAutoGrow(method, context)) { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); if (lay) { SimpleRootAreaProvider *provider = dynamic_cast(lay->provider()); if (provider) { provider->m_fixAutogrow = true; } } } #endif bool answer = loadOdfFrame(element, context); m_textShapeData->document()->setUndoRedoEnabled(true); return answer; } bool TextShape::loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context) { // If the loadOdfFrame from the base class for draw:text-box fails, check // for table:table, because that is a legal child of draw:frame in ODF 1.2. if (!KoFrameShape::loadOdfFrame(element, context)) { const KoXmlElement &possibleTableElement(KoXml::namedItemNS(element, KoXmlNS::table, "table")); if (possibleTableElement.isNull()) { return false; } else { return loadOdfFrameElement(possibleTableElement, context); } } return true; } bool TextShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { bool ok = m_textShapeData->loadOdf(element, context, 0, this); if (ok) { ShrinkToFitShapeContainer::tryWrapShape(this, element, context); } return ok; } void TextShape::update() const { KoShapeContainer::update(); } void TextShape::updateAbsolute(const QRectF &shape) const { // this is done to avoid updates which are called during the paint event and not needed. //if (!m_paintRegion.contains(shape.toRect())) { //KoShape::update(shape); //} // FIXME: just a hack to remove outdated call from the deprecated shape KoShape::updateAbsolute(shape); } void TextShape::waitUntilReady(const KoViewConverter &, bool asynchronous) const { Q_UNUSED(asynchronous); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); if (m_textShapeData->isDirty()) { // Do a simple layout-call which will make sure to relayout till things are done. If more // layouts are scheduled then we don't need to wait for them here but can just continue. lay->layout(); } } KoImageCollection *TextShape::imageCollection() { return m_imageCollection; } void TextShape::updateDocumentData() { if (m_layout) { KoTextDocument document(m_textShapeData->document()); m_layout->setStyleManager(document.styleManager()); m_layout->setInlineTextObjectManager(document.inlineTextObjectManager()); m_layout->setTextRangeManager(document.textRangeManager()); m_layout->setChangeTracker(document.changeTracker()); } } diff --git a/plugins/flake/textshape/kotext/KoTableColumnAndRowStyleManager.cpp b/plugins/flake/textshape/kotext/KoTableColumnAndRowStyleManager.cpp index 02e003a719..363a74f0d3 100644 --- a/plugins/flake/textshape/kotext/KoTableColumnAndRowStyleManager.cpp +++ b/plugins/flake/textshape/kotext/KoTableColumnAndRowStyleManager.cpp @@ -1,242 +1,242 @@ /* This file is part of the KDE project * Copyright (C) 2009 KO GmbH * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 "KoTableColumnAndRowStyleManager.h" #include "styles/KoTableColumnStyle.h" #include "styles/KoTableRowStyle.h" #include "styles/KoTableCellStyle.h" #include "styles/KoTableStyle.h" #include #include #include #include "TextDebug.h" class Q_DECL_HIDDEN KoTableColumnAndRowStyleManager::Private : public QSharedData { public: - Private() { } + Private() : QSharedData() { } ~Private() { } QVector tableColumnStyles; QVector tableRowStyles; QVector defaultRowCellStyles; QVector defaultColumnCellStyles; }; KoTableColumnAndRowStyleManager::KoTableColumnAndRowStyleManager() : d(new Private()) { } KoTableColumnAndRowStyleManager::KoTableColumnAndRowStyleManager(const KoTableColumnAndRowStyleManager &rhs) : d(rhs.d) { } KoTableColumnAndRowStyleManager &KoTableColumnAndRowStyleManager::operator=(const KoTableColumnAndRowStyleManager &rhs) { d = rhs.d; return *this; } KoTableColumnAndRowStyleManager::~KoTableColumnAndRowStyleManager() { } KoTableColumnAndRowStyleManager KoTableColumnAndRowStyleManager::getManager(QTextTable *table) { QTextTableFormat tableFormat = table->format(); if (tableFormat.hasProperty(KoTableStyle::ColumnAndRowStyleManager)) { return tableFormat.property(KoTableStyle::ColumnAndRowStyleManager).value(); } else { KoTableColumnAndRowStyleManager carsManager; QVariant var; var.setValue(carsManager); tableFormat.setProperty(KoTableStyle::ColumnAndRowStyleManager, var); table->setFormat(tableFormat); return carsManager; } } void KoTableColumnAndRowStyleManager::setColumnStyle(int column, const KoTableColumnStyle &columnStyle) { Q_ASSERT(column >= 0); if (column < 0) { return; } if (column < d->tableColumnStyles.size() && d->tableColumnStyles.value(column) == columnStyle) { return; } while (column >= d->tableColumnStyles.size()) d->tableColumnStyles.append(KoTableColumnStyle()); d->tableColumnStyles.replace(column, columnStyle); } void KoTableColumnAndRowStyleManager::insertColumns(int column, int numberColumns, const KoTableColumnStyle &columnStyle) { Q_ASSERT(column >= 0); Q_ASSERT(numberColumns >= 0); if (column < 0 || numberColumns < 0) { return; } while (column > d->tableColumnStyles.size()) d->tableColumnStyles.append(KoTableColumnStyle()); d->tableColumnStyles.insert(column, numberColumns, columnStyle); } void KoTableColumnAndRowStyleManager::removeColumns(int column, int numberColumns) { Q_ASSERT(column >= 0); Q_ASSERT(numberColumns >= 0); if (column >= d->tableColumnStyles.size() || column < 0 || numberColumns < 0) { return; } while (column > d->tableColumnStyles.size()) d->tableColumnStyles.append(KoTableColumnStyle()); d->tableColumnStyles.remove(column, numberColumns); } KoTableColumnStyle KoTableColumnAndRowStyleManager::columnStyle(int column) const { Q_ASSERT(column >= 0); if (column < 0) { return KoTableColumnStyle(); } return d->tableColumnStyles.value(column); } void KoTableColumnAndRowStyleManager::setRowStyle(int row, const KoTableRowStyle &rowStyle) { Q_ASSERT(row >= 0); if (row < 0) { return; } if (row < d->tableRowStyles.size() && d->tableRowStyles.value(row) == rowStyle) { return; } while (row >= d->tableRowStyles.size()) d->tableRowStyles.append(KoTableRowStyle()); d->tableRowStyles.replace(row, rowStyle); } void KoTableColumnAndRowStyleManager::insertRows(int row, int numberRows, const KoTableRowStyle &rowStyle) { Q_ASSERT(row >= 0); Q_ASSERT(numberRows >= 0); if (row < 0 || numberRows < 0) { return; } while (row > d->tableRowStyles.size()) d->tableRowStyles.append(KoTableRowStyle()); d->tableRowStyles.insert(row, numberRows, rowStyle); } void KoTableColumnAndRowStyleManager::removeRows(int row, int numberRows) { Q_ASSERT(row >= 0); Q_ASSERT(numberRows >= 0); if (row >= d->tableRowStyles.size() || row < 0 || numberRows < 0) { return; } while (row > d->tableRowStyles.size()) d->tableRowStyles.append(KoTableRowStyle()); d->tableRowStyles.remove(row, numberRows); } KoTableRowStyle KoTableColumnAndRowStyleManager::rowStyle(int row) const { Q_ASSERT(row >= 0); if (row < 0) { return KoTableRowStyle(); } return d->tableRowStyles.value(row); } KoTableCellStyle* KoTableColumnAndRowStyleManager::defaultColumnCellStyle(int column) const { Q_ASSERT(column >= 0); return d->defaultColumnCellStyles.value(column); } void KoTableColumnAndRowStyleManager::setDefaultColumnCellStyle(int column, KoTableCellStyle* cellStyle) { Q_ASSERT(column >= 0); if (column < d->defaultColumnCellStyles.size() && d->defaultColumnCellStyles.value(column) == cellStyle) { return; } while (column > d->defaultColumnCellStyles.size()) d->defaultColumnCellStyles.append(0); d->defaultColumnCellStyles.append(cellStyle); } KoTableCellStyle* KoTableColumnAndRowStyleManager::defaultRowCellStyle(int row) const { Q_ASSERT(row >= 0); return d->defaultRowCellStyles.value(row); } void KoTableColumnAndRowStyleManager::setDefaultRowCellStyle(int row, KoTableCellStyle* cellStyle) { Q_ASSERT(row >= 0); if (row < d->defaultRowCellStyles.size() && d->defaultRowCellStyles.value(row) == cellStyle) { return; } while (row > d->defaultRowCellStyles.size()) d->defaultRowCellStyles.append(0); d->defaultRowCellStyles.append(cellStyle); } diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index c2927d07ba..51056ecf47 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,53 +1,52 @@ project(kritafilters) add_subdirectory(libkra) if(CMAKE_SIZEOF_VOID_P EQUAL 4) add_definitions( -DCPU_32_BITS ) endif() if(JPEG_FOUND AND HAVE_LCMS2) add_subdirectory(jpeg) endif() if(TIFF_FOUND) add_subdirectory(tiff) endif() if(PNG_FOUND) add_subdirectory(png) add_subdirectory(csv) endif() if(OPENEXR_FOUND) add_subdirectory(exr) endif() if(Poppler_Qt5_FOUND) add_subdirectory(pdf) endif() if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(qimageio) add_subdirectory(ora) -add_subdirectory(ppm) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(kra) if (GIF_FOUND) add_subdirectory(gif) endif() if (HEIF_FOUND) add_subdirectory(heif) endif() diff --git a/plugins/impex/brush/kis_brush_export.cpp b/plugins/impex/brush/kis_brush_export.cpp index 981ac59956..2bf6d6925b 100644 --- a/plugins/impex/brush/kis_brush_export.cpp +++ b/plugins/impex/brush/kis_brush_export.cpp @@ -1,252 +1,252 @@ /* * 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 #include #include #include #include struct KisBrushExportOptions { qreal spacing; bool mask; int brushStyle; int dimensions; qint32 ranks[KisPipeBrushParasite::MaxDim]; qint32 selectionModes[KisPipeBrushParasite::MaxDim]; QString name; }; K_PLUGIN_FACTORY_WITH_JSON(KisBrushExportFactory, "krita_brush_export.json", registerPlugin();) KisBrushExport::KisBrushExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushExport::~KisBrushExport() { } KisImportExportErrorCode 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.brushStyle = configuration->getInt("brushStyle"); exportOptions.dimensions = configuration->getInt("dimensions"); for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { exportOptions.selectionModes[i] = configuration->getInt("selectionMode" + QString::number(i)); exportOptions.ranks[i] = configuration->getInt("rank" + QString::number(i)); } 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 ImportExportCodes::FileFormatIncorrect; } qApp->processEvents(); // For vector layers to be updated QRect rc = document->savingImage()->bounds(); brush->setSpacing(exportOptions.spacing); 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) { // push_front to behave exactly as gimp for gih creation devices[0].push_front(node->projection().data()); } QVector modes; for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { switch (exportOptions.selectionModes[i]) { 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; case 5: modes.push_back(KisParasite::Velocity); break; default: modes.push_back(KisParasite::Incremental); } } KisPipeBrushParasite parasite; parasite.dim = exportOptions.dimensions; parasite.ncells = devices.at(0).count(); int maxRanks = 0; for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { // ### This can mask some bugs, be careful here in the future parasite.rank[i] = exportOptions.ranks[i]; parasite.selection[i] = modes.at(i); maxRanks += exportOptions.ranks[i]; } if (maxRanks > layers.count()) { return ImportExportCodes::FileFormatIncorrect; } // XXX needs movement! parasite.setBrushesCount(); pipeBrush->setParasite(parasite); pipeBrush->setDevices(devices, rc.width(), rc.height()); if (exportOptions.mask) { QVector brushes = pipeBrush->brushes(); Q_FOREACH(KisGbrBrush* brush, brushes) { brush->setHasColor(false); } } } else { if (exportOptions.mask) { QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); brush->setImage(image); brush->setBrushTipImage(image); } else { brush->initFromPaintDev(document->savingImage()->projection(),0,0,rc.width(), rc.height()); } } brush->setName(exportOptions.name); // brushes are created after devices are loaded, call mask mode after that brush->setUseColorAsMask(exportOptions.mask); brush->setWidth(rc.width()); brush->setHeight(rc.height()); if (brush->saveToDevice(io)) { return ImportExportCodes::OK; } else { return ImportExportCodes::Failure; } } 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("brushStyle", 0); cfg->setProperty("dimensions", 1); for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) { cfg->setProperty("selectionMode" + QString::number(i), 2); cfg->getInt("rank" + QString::number(i), 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); wdg->animStyleGroup->setVisible(false); } else if (to == "image/x-gimp-brush-animated") { wdg->groupBox->setVisible(true); wdg->animStyleGroup->setVisible(true); } // preload gih name with chosen filename QFileInfo fileLocation(filename()); - wdg->nameLineEdit->setText(fileLocation.baseName()); + wdg->nameLineEdit->setText(fileLocation.completeBaseName()); 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)); } } #include "kis_brush_export.moc" diff --git a/plugins/impex/libkra/kis_kra_savexml_visitor.cpp b/plugins/impex/libkra/kis_kra_savexml_visitor.cpp index c1ad7058ec..35343d3adf 100644 --- a/plugins/impex/libkra/kis_kra_savexml_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_savexml_visitor.cpp @@ -1,497 +1,500 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_savexml_visitor.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_layer_properties_icons.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 "kis_keyframe_channel.h" #include "kis_dom_utils.h" using namespace KRA; KisSaveXmlVisitor::KisSaveXmlVisitor(QDomDocument doc, const QDomElement & element, quint32 &count, const QString &url, bool root) : KisNodeVisitor() , m_doc(doc) , m_count(count) , m_url(url) , m_root(root) { Q_ASSERT(!element.isNull()); m_elem = element; } void KisSaveXmlVisitor::setSelectedNodes(vKisNodeSP selectedNodes) { m_selectedNodes = selectedNodes; } QStringList KisSaveXmlVisitor::errorMessages() const { return m_errorMessages; } bool KisSaveXmlVisitor::visit(KisExternalLayer * layer) { if (layer->inherits("KisReferenceImagesLayer")) { return saveReferenceImagesLayer(layer); } else if (layer->inherits("KisShapeLayer")) { QDomElement layerElement = m_doc.createElement(LAYER); saveLayer(layerElement, SHAPE_LAYER, layer); m_elem.appendChild(layerElement); m_count++; return saveMasks(layer, layerElement); } else if (layer->inherits("KisFileLayer")) { QDomElement layerElement = m_doc.createElement(LAYER); saveLayer(layerElement, FILE_LAYER, layer); KisFileLayer *fileLayer = dynamic_cast(layer); QString path = fileLayer->path(); QDir d(QFileInfo(m_url).absolutePath()); layerElement.setAttribute("source", d.relativeFilePath(path)); if (fileLayer->scalingMethod() == KisFileLayer::ToImagePPI) { layerElement.setAttribute("scale", "true"); } else { layerElement.setAttribute("scale", "false"); } layerElement.setAttribute("scalingmethod", (int)fileLayer->scalingMethod()); layerElement.setAttribute(COLORSPACE_NAME, layer->original()->colorSpace()->id()); m_elem.appendChild(layerElement); m_count++; return saveMasks(layer, layerElement); } return false; } QDomElement KisSaveXmlVisitor::savePaintLayerAttributes(KisPaintLayer *layer, QDomDocument &doc) { QDomElement element = doc.createElement(LAYER); saveLayer(element, PAINT_LAYER, layer); element.setAttribute(CHANNEL_LOCK_FLAGS, flagsToString(layer->channelLockFlags())); element.setAttribute(COLORSPACE_NAME, layer->paintDevice()->colorSpace()->id()); element.setAttribute(ONION_SKIN_ENABLED, layer->onionSkinEnabled()); element.setAttribute(VISIBLE_IN_TIMELINE, layer->useInTimeline()); return element; } void KisSaveXmlVisitor::loadPaintLayerAttributes(const QDomElement &el, KisPaintLayer *layer) { loadLayerAttributes(el, layer); if (el.hasAttribute(CHANNEL_LOCK_FLAGS)) { layer->setChannelLockFlags(stringToFlags(el.attribute(CHANNEL_LOCK_FLAGS))); } } bool KisSaveXmlVisitor::visit(KisPaintLayer *layer) { QDomElement layerElement = savePaintLayerAttributes(layer, m_doc); m_elem.appendChild(layerElement); m_count++; return saveMasks(layer, layerElement); } bool KisSaveXmlVisitor::visit(KisGroupLayer *layer) { QDomElement layerElement; if (m_root) // if this is the root we fake so not to save it layerElement = m_elem; else { layerElement = m_doc.createElement(LAYER); saveLayer(layerElement, GROUP_LAYER, layer); layerElement.setAttribute(PASS_THROUGH_MODE, layer->passThroughMode()); m_elem.appendChild(layerElement); } QDomElement elem = m_doc.createElement(LAYERS); Q_ASSERT(!layerElement.isNull()); layerElement.appendChild(elem); KisSaveXmlVisitor visitor(m_doc, elem, m_count, m_url, false); visitor.setSelectedNodes(m_selectedNodes); m_count++; bool success = visitor.visitAllInverse(layer); m_errorMessages.append(visitor.errorMessages()); if (!m_errorMessages.isEmpty()) { return false; } QMapIterator i(visitor.nodeFileNames()); while (i.hasNext()) { i.next(); m_nodeFileNames[i.key()] = i.value(); } i = QMapIterator(visitor.keyframeFileNames()); while (i.hasNext()) { i.next(); m_keyframeFileNames[i.key()] = i.value(); } return success; } bool KisSaveXmlVisitor::visit(KisAdjustmentLayer* layer) { if (!layer->filter()) { return false; } QDomElement layerElement = m_doc.createElement(LAYER); saveLayer(layerElement, ADJUSTMENT_LAYER, layer); layerElement.setAttribute(FILTER_NAME, layer->filter()->name()); layerElement.setAttribute(FILTER_VERSION, layer->filter()->version()); m_elem.appendChild(layerElement); m_count++; return saveMasks(layer, layerElement); } bool KisSaveXmlVisitor::visit(KisGeneratorLayer *layer) { QDomElement layerElement = m_doc.createElement(LAYER); saveLayer(layerElement, GENERATOR_LAYER, layer); layerElement.setAttribute(GENERATOR_NAME, layer->filter()->name()); layerElement.setAttribute(GENERATOR_VERSION, layer->filter()->version()); m_elem.appendChild(layerElement); m_count++; return saveMasks(layer, layerElement); } bool KisSaveXmlVisitor::visit(KisCloneLayer *layer) { QDomElement layerElement = m_doc.createElement(LAYER); saveLayer(layerElement, CLONE_LAYER, layer); layerElement.setAttribute(CLONE_FROM, layer->copyFromInfo().name()); layerElement.setAttribute(CLONE_FROM_UUID, layer->copyFromInfo().uuid().toString()); layerElement.setAttribute(CLONE_TYPE, layer->copyType()); m_elem.appendChild(layerElement); m_count++; return saveMasks(layer, layerElement); } bool KisSaveXmlVisitor::visit(KisFilterMask *mask) { Q_ASSERT(mask); if (!mask->filter()) { return false; } QDomElement el = m_doc.createElement(MASK); saveMask(el, FILTER_MASK, mask); el.setAttribute(FILTER_NAME, mask->filter()->name()); el.setAttribute(FILTER_VERSION, mask->filter()->version()); m_elem.appendChild(el); m_count++; return true; } bool KisSaveXmlVisitor::visit(KisTransformMask *mask) { Q_ASSERT(mask); QDomElement el = m_doc.createElement(MASK); saveMask(el, TRANSFORM_MASK, mask); m_elem.appendChild(el); m_count++; return true; } bool KisSaveXmlVisitor::visit(KisTransparencyMask *mask) { Q_ASSERT(mask); QDomElement el = m_doc.createElement(MASK); saveMask(el, TRANSPARENCY_MASK, mask); m_elem.appendChild(el); m_count++; return true; } bool KisSaveXmlVisitor::visit(KisColorizeMask *mask) { Q_ASSERT(mask); QDomElement el = m_doc.createElement(MASK); saveMask(el, COLORIZE_MASK, mask); m_elem.appendChild(el); m_count++; return true; } bool KisSaveXmlVisitor::visit(KisSelectionMask *mask) { Q_ASSERT(mask); QDomElement el = m_doc.createElement(MASK); saveMask(el, SELECTION_MASK, mask); m_elem.appendChild(el); m_count++; return true; } void KisSaveXmlVisitor::loadLayerAttributes(const QDomElement &el, KisLayer *layer) { if (el.hasAttribute(NAME)) { QString layerName = el.attribute(NAME); - KIS_ASSERT_RECOVER_RETURN(layerName == layer->name()); + if (layerName != layer->name()) { + // Make the EXR layername leading in case of conflicts + layer->setName(layerName); + } } if (el.hasAttribute(CHANNEL_FLAGS)) { layer->setChannelFlags(stringToFlags(el.attribute(CHANNEL_FLAGS))); } if (el.hasAttribute(OPACITY)) { layer->setOpacity(el.attribute(OPACITY).toInt()); } if (el.hasAttribute(COMPOSITE_OP)) { layer->setCompositeOpId(el.attribute(COMPOSITE_OP)); } if (el.hasAttribute(VISIBLE)) { layer->setVisible(el.attribute(VISIBLE).toInt()); } if (el.hasAttribute(LOCKED)) { layer->setUserLocked(el.attribute(LOCKED).toInt()); } if (el.hasAttribute(X)) { layer->setX(el.attribute(X).toInt()); } if (el.hasAttribute(Y)) { layer->setY(el.attribute(Y).toInt()); } if (el.hasAttribute(UUID)) { layer->setUuid(el.attribute(UUID)); } if (el.hasAttribute(COLLAPSED)) { layer->setCollapsed(el.attribute(COLLAPSED).toInt()); } if (el.hasAttribute(COLOR_LABEL)) { layer->setColorLabelIndex(el.attribute(COLOR_LABEL).toInt()); } if (el.hasAttribute(VISIBLE_IN_TIMELINE)) { layer->setUseInTimeline(el.attribute(VISIBLE_IN_TIMELINE).toInt()); } if (el.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = el.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } void KisSaveXmlVisitor::saveNodeKeyframes(const KisNode* node, QString nodeFilename, QDomElement& nodeElement) { if (node->isAnimated()) { QString keyframeFile = nodeFilename + ".keyframes.xml"; m_keyframeFileNames[node] = keyframeFile; nodeElement.setAttribute(KEYFRAME_FILE, keyframeFile); } } void KisSaveXmlVisitor::saveLayer(QDomElement & el, const QString & layerType, const KisLayer * layer) { QString filename = LAYER + QString::number(m_count); el.setAttribute(CHANNEL_FLAGS, flagsToString(layer->channelFlags())); el.setAttribute(NAME, layer->name()); el.setAttribute(OPACITY, layer->opacity()); el.setAttribute(COMPOSITE_OP, layer->compositeOp()->id()); el.setAttribute(VISIBLE, layer->visible()); el.setAttribute(LOCKED, layer->userLocked()); el.setAttribute(NODE_TYPE, layerType); el.setAttribute(FILE_NAME, filename); el.setAttribute(X, layer->x()); el.setAttribute(Y, layer->y()); el.setAttribute(UUID, layer->uuid().toString()); el.setAttribute(COLLAPSED, layer->collapsed()); el.setAttribute(COLOR_LABEL, layer->colorLabelIndex()); el.setAttribute(VISIBLE_IN_TIMELINE, layer->useInTimeline()); if (layer->layerStyle()) { el.setAttribute(LAYER_STYLE_UUID, layer->layerStyle()->uuid().toString()); } Q_FOREACH (KisNodeSP node, m_selectedNodes) { if (node.data() == layer) { el.setAttribute("selected", "true"); break; } } saveNodeKeyframes(layer, filename, el); m_nodeFileNames[layer] = filename; dbgFile << "Saved layer " << layer->name() << " of type " << layerType << " with filename " << LAYER + QString::number(m_count); } void KisSaveXmlVisitor::saveMask(QDomElement & el, const QString & maskType, const KisMaskSP mask) { QString filename = MASK + QString::number(m_count); el.setAttribute(NAME, mask->name()); el.setAttribute(VISIBLE, mask->visible()); el.setAttribute(LOCKED, mask->userLocked()); el.setAttribute(NODE_TYPE, maskType); el.setAttribute(FILE_NAME, filename); el.setAttribute(X, mask->x()); el.setAttribute(Y, mask->y()); el.setAttribute(UUID, mask->uuid().toString()); if (maskType == SELECTION_MASK) { el.setAttribute(ACTIVE, mask->nodeProperties().boolProperty("active")); } else if (maskType == COLORIZE_MASK) { el.setAttribute(COLORSPACE_NAME, mask->colorSpace()->id()); el.setAttribute(COMPOSITE_OP, mask->compositeOpId()); el.setAttribute(COLORIZE_EDIT_KEYSTROKES, KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()); el.setAttribute(COLORIZE_SHOW_COLORING, KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool()); const KisColorizeMask *colorizeMask = dynamic_cast(mask.data()); KIS_SAFE_ASSERT_RECOVER_NOOP(colorizeMask); if (colorizeMask) { el.setAttribute(COLORIZE_USE_EDGE_DETECTION, colorizeMask->useEdgeDetection()); el.setAttribute(COLORIZE_EDGE_DETECTION_SIZE, KisDomUtils::toString(colorizeMask->edgeDetectionSize())); el.setAttribute(COLORIZE_FUZZY_RADIUS, KisDomUtils::toString(colorizeMask->fuzzyRadius())); el.setAttribute(COLORIZE_CLEANUP, int(100 * colorizeMask->cleanUpAmount())); el.setAttribute(COLORIZE_LIMIT_TO_DEVICE, colorizeMask->limitToDeviceBounds()); } } saveNodeKeyframes(mask, filename, el); m_nodeFileNames[mask] = filename; dbgFile << "Saved mask " << mask->name() << " of type " << maskType << " with filename " << filename; } bool KisSaveXmlVisitor::saveMasks(KisNode * node, QDomElement & layerElement) { if (node->childCount() > 0) { QDomElement elem = m_doc.createElement(MASKS); Q_ASSERT(!layerElement.isNull()); layerElement.appendChild(elem); KisSaveXmlVisitor visitor(m_doc, elem, m_count, m_url, false); visitor.setSelectedNodes(m_selectedNodes); bool success = visitor.visitAllInverse(node); m_errorMessages.append(visitor.errorMessages()); if (!m_errorMessages.isEmpty()) { return false; } QMapIterator i(visitor.nodeFileNames()); while (i.hasNext()) { i.next(); m_nodeFileNames[i.key()] = i.value(); } i = QMapIterator(visitor.keyframeFileNames()); while (i.hasNext()) { i.next(); m_keyframeFileNames[i.key()] = i.value(); } return success; } return true; } bool KisSaveXmlVisitor::saveReferenceImagesLayer(KisExternalLayer *layer) { auto *referencesLayer = dynamic_cast(layer); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(referencesLayer, false) QDomElement layerElement = m_doc.createElement(LAYER); layerElement.setAttribute(NODE_TYPE, REFERENCE_IMAGES_LAYER); int nextId = 0; Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); reference->saveXml(m_doc, layerElement, nextId); nextId++; } m_elem.appendChild(layerElement); m_count++; return true; } diff --git a/plugins/impex/png/tests/kis_png_test.cpp b/plugins/impex/png/tests/kis_png_test.cpp index 2259cb7822..912286acc7 100644 --- a/plugins/impex/png/tests/kis_png_test.cpp +++ b/plugins/impex/png/tests/kis_png_test.cpp @@ -1,162 +1,179 @@ /* * 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_png_test.h" #include #include #include "filestest.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif const QString PngMimetype = "image/png"; void KisPngTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } void KisPngTest::testWriteonly() { TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PngMimetype); } void roudTripHdrImage(const KoColorSpace *savingColorSpace) { - qDebug() << "Test saving" << savingColorSpace->id() << savingColorSpace->profile()->name(); - const KoColorSpace * scRGBF32 = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p709G10Profile()); KoColor fillColor(scRGBF32); float *pixelPtr = reinterpret_cast(fillColor.data()); pixelPtr[0] = 2.7; pixelPtr[1] = 1.6; pixelPtr[2] = 0.8; pixelPtr[3] = 0.9; { QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(0, 3, 3, scRGBF32, "png test"); KisPaintLayerSP paintLayer0 = new KisPaintLayer(image, "paint0", OPACITY_OPAQUE_U8); paintLayer0->paintDevice()->fill(image->bounds(), fillColor); image->addNode(paintLayer0, image->root()); // convert image color space before saving image->convertImageColorSpace(savingColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); doc->setCurrentImage(image); KisPropertiesConfigurationSP exportConfiguration = new KisPropertiesConfiguration(); exportConfiguration->setProperty("saveAsHDR", true); exportConfiguration->setProperty("saveSRGBProfile", false); exportConfiguration->setProperty("forceSRGB", false); doc->exportDocumentSync(QUrl::fromLocalFile("test.png"), "image/png", exportConfiguration); } { QScopedPointer doc(KisPart::instance()->createDocument()); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); KisImportExportErrorCode loadingStatus = manager.importDocument("test.png", QString()); QVERIFY(loadingStatus.isOk()); KisImageSP image = doc->image(); image->initialRefreshGraph(); KoColor resultColor; // qDebug() << ppVar(image->colorSpace()) << image->colorSpace()->profile()->name(); // image->projection()->pixel(1, 1, &resultColor); // qDebug() << ppVar(resultColor); image->convertImageColorSpace(scRGBF32, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); image->projection()->pixel(1, 1, &resultColor); // qDebug() << ppVar(resultColor); const float tolerance = savingColorSpace->colorDepthId() == Integer8BitsColorDepthID ? 0.02 : 0.01; bool resultIsValid = true; float *resultPtr = reinterpret_cast(resultColor.data()); for (int i = 0; i < 4; i++) { resultIsValid &= qAbs(resultPtr[i] - pixelPtr[i]) < tolerance; } if (!resultIsValid) { qDebug() << ppVar(fillColor) << ppVar(resultColor); } QVERIFY(resultIsValid); } } void KisPngTest::testSaveHDR() { QVector colorDepthIds; colorDepthIds << Float16BitsColorDepthID; colorDepthIds << Float32BitsColorDepthID; QVector profiles; - profiles << KoColorSpaceRegistry::instance()->p709G10Profile(); - profiles << KoColorSpaceRegistry::instance()->p2020G10Profile(); - profiles << KoColorSpaceRegistry::instance()->p2020PQProfile(); - + const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709G10Profile(); + if (!profile) { + qWarning() << "Could not get a p709G10 Profile"; + } + else { + profiles << profile; + } + profile = KoColorSpaceRegistry::instance()->p2020G10Profile(); + if (!profile) { + qWarning() << "Could not get a p2020G10 Profile"; + } + else { + profiles << profile; + } + profile = KoColorSpaceRegistry::instance()->p2020PQProfile();; + if (!profile) { + qWarning() << "Could not get a p2020PQ Profile"; + } + else { + profiles << profile; + } Q_FOREACH(const KoID &depth, colorDepthIds) { Q_FOREACH(const KoColorProfile *profile, profiles) { - roudTripHdrImage( - KoColorSpaceRegistry::instance()->colorSpace( - RGBAColorModelID.id(), - depth.id(), - profile)); + if (profile) { + roudTripHdrImage( + KoColorSpaceRegistry::instance()->colorSpace( + RGBAColorModelID.id(), + depth.id(), + profile)); + } } } roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile())); roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile())); } KISTEST_MAIN(KisPngTest) diff --git a/plugins/impex/ppm/CMakeLists.txt b/plugins/impex/ppm/CMakeLists.txt deleted file mode 100644 index dc0bac82cb..0000000000 --- a/plugins/impex/ppm/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -add_subdirectory(tests) - -include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) - -set(kritappmimport_SOURCES - kis_ppm_import.cpp - ) - -add_library(kritappmimport MODULE ${kritappmimport_SOURCES}) - -target_link_libraries(kritappmimport kritaui ) - -install(TARGETS kritappmimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) - -set(kritappmexport_SOURCES - kis_ppm_export.cpp - ) - -ki18n_wrap_ui(kritappmexport_SOURCES kis_wdg_options_ppm.ui ) - -add_library(kritappmexport MODULE ${kritappmexport_SOURCES}) - -target_link_libraries(kritappmexport kritaui kritaimpex) - -install(TARGETS kritappmexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) - -install( PROGRAMS krita_ppm.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/ppm/kis_ppm_export.cpp b/plugins/impex/ppm/kis_ppm_export.cpp deleted file mode 100644 index e76bccd566..0000000000 --- a/plugins/impex/ppm/kis_ppm_export.cpp +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (c) 2009 Cyrille Berger - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 2 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that 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_ppm_export.h" - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "kis_iterator_ng.h" - -#include - - -K_PLUGIN_FACTORY_WITH_JSON(KisPPMExportFactory, "krita_ppm_export.json", registerPlugin();) - -KisPPMExport::KisPPMExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) -{ -} - -KisPPMExport::~KisPPMExport() -{ -} - -class KisPPMFlow -{ -public: - KisPPMFlow() { - } - virtual ~KisPPMFlow() { - } - virtual void writeBool(quint8 v) = 0; - virtual void writeBool(quint16 v) = 0; - virtual void writeNumber(quint8 v) = 0; - virtual void writeNumber(quint16 v) = 0; - virtual void flush() = 0; -private: -}; - -class KisPPMAsciiFlow : public KisPPMFlow -{ -public: - KisPPMAsciiFlow(QIODevice* device) : m_device(device) { - } - ~KisPPMAsciiFlow() override { - } - void writeBool(quint8 v) override { - if (v > 127) { - m_device->write("1 "); - } else { - m_device->write("0 "); - } - } - void writeBool(quint16 v) override { - writeBool(quint8(v >> 8)); - } - void writeNumber(quint8 v) override { - m_device->write(QByteArray::number(v)); - m_device->write(" "); - } - void writeNumber(quint16 v) override { - m_device->write(QByteArray::number(v)); - m_device->write(" "); - } - void flush() override { - } -private: - QIODevice* m_device; -}; - -class KisPPMBinaryFlow : public KisPPMFlow -{ -public: - KisPPMBinaryFlow(QIODevice* device) : m_device(device), m_pos(0), m_current(0) { - } - ~KisPPMBinaryFlow() override { - } - void writeBool(quint8 v) override { - m_current = m_current << 1; - m_current |= (v > 127); - ++m_pos; - if (m_pos >= 8) { - m_current = 0; - m_pos = 0; - flush(); - } - } - void writeBool(quint16 v) override { - writeBool(quint8(v >> 8)); - } - void writeNumber(quint8 v) override { - m_device->write((char*)&v, 1); - } - void writeNumber(quint16 v) override { - quint16 vo = qToBigEndian(v); - m_device->write((char*)&vo, 2); - } - void flush() override { - m_device->write((char*)&m_current, 1); - } -private: - QIODevice* m_device; - int m_pos; - quint8 m_current; -}; - -KisImportExportErrorCode KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) -{ - bool rgb = (mimeType() == "image/x-portable-pixmap"); - bool binary = (configuration->getInt("type") == 0); - - bool bitmap = (mimeType() == "image/x-portable-bitmap"); - - KisImageSP image = document->savingImage(); - KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); - - // Test color space - if (((rgb && (pd->colorSpace()->id() != "RGBA" && pd->colorSpace()->id() != "RGBA16")) - || (!rgb && (pd->colorSpace()->id() != "GRAYA" && pd->colorSpace()->id() != "GRAYA16" && pd->colorSpace()->id() != "GRAYAU16")))) { - if (rgb) { - pd->convertTo(KoColorSpaceRegistry::instance()->rgb8(0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - } - else { - pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - } - } - - bool is16bit = pd->colorSpace()->id() == "RGBA16" || pd->colorSpace()->id() == "GRAYAU16"; - - // Write the magic - QString toWrite = ""; - int written = 0; - if (rgb) { - if (binary) { - toWrite = "P6"; - } - else { - toWrite = "P3"; - } - } else if (bitmap) { - if (binary) { - toWrite = "P4"; - } - else { - toWrite = "P1"; - } - } else { - if (binary) { - toWrite = "P5"; - } - else { - toWrite = "P2"; - } - } - written = io->write(toWrite.toUtf8()); - if (written != toWrite.toUtf8().length()) { - return ImportExportCodes::ErrorWhileWriting; - } - - written = io->write("\n"); - - // Write the header - QByteArray width = QByteArray::number(image->width()); - QByteArray height = QByteArray::number(image->height()); - - written += io->write(width); - written += io->write(" "); - written += io->write(height); - if (written != QString(" ").length() + QString("\n").length() + width.length() + height.length()) { - return ImportExportCodes::ErrorWhileWriting; - } - if (!bitmap) { - if (is16bit) { - toWrite = " 65535\n"; - } - else { - toWrite = " 255\n"; - } - } else { - toWrite = "\n"; - } - written = io->write(toWrite.toUtf8()); - if (written != toWrite.toUtf8().length()) { - return ImportExportCodes::ErrorWhileWriting; - } - - // Write the data - KisPPMFlow* flow = 0; - if (binary) flow = new KisPPMBinaryFlow(io); - else flow = new KisPPMAsciiFlow(io); - - for (int y = 0; y < image->height(); ++y) { - KisHLineIteratorSP it = pd->createHLineIteratorNG(0, y, image->width()); - if (is16bit) { - if (rgb) { - do { - flow->writeNumber(KoBgrU16Traits::red(it->rawData())); - flow->writeNumber(KoBgrU16Traits::green(it->rawData())); - flow->writeNumber(KoBgrU16Traits::blue(it->rawData())); - - } while (it->nextPixel()); - } else if (bitmap) { - do { - flow->writeBool(*reinterpret_cast(it->rawData())); - - } while (it->nextPixel()); - } else { - do { - flow->writeNumber(*reinterpret_cast(it->rawData())); - } while (it->nextPixel()); - } - } else { - if (rgb) { - do { - flow->writeNumber(KoBgrTraits::red(it->rawData())); - flow->writeNumber(KoBgrTraits::green(it->rawData())); - flow->writeNumber(KoBgrTraits::blue(it->rawData())); - - } while (it->nextPixel()); - } else if (bitmap) { - do { - flow->writeBool(*reinterpret_cast(it->rawData())); - - } while (it->nextPixel()); - } else { - do { - flow->writeNumber(*reinterpret_cast(it->rawData())); - - } while (it->nextPixel()); - } - } - } - if (bitmap) { - flow->flush(); - } - delete flow; - return ImportExportCodes::OK; -} - -KisPropertiesConfigurationSP KisPPMExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const -{ - KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); - cfg->setProperty("type", 0); - return cfg; -} - -KisConfigWidget *KisPPMExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const -{ - return new KisWdgOptionsPPM(parent); -} - - -void KisWdgOptionsPPM::setConfiguration(const KisPropertiesConfigurationSP cfg) -{ - cmbType->setCurrentIndex(cfg->getInt("type", 0)); -} - -KisPropertiesConfigurationSP KisWdgOptionsPPM::configuration() const -{ - KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); - cfg->setProperty("type", cmbType->currentIndex()); - return cfg; - -} - -void KisPPMExport::initializeCapabilities() -{ - addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); - addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); - addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); - addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); - - QList > supportedColorModels; - supportedColorModels << QPair() - << QPair(RGBAColorModelID, Integer8BitsColorDepthID) - << QPair(RGBAColorModelID, Integer16BitsColorDepthID) - << QPair(GrayAColorModelID, Integer8BitsColorDepthID) - << QPair(GrayAColorModelID, Integer16BitsColorDepthID); - addSupportedColorModels(supportedColorModels, "PPM"); - -} - - - -#include "kis_ppm_export.moc" - diff --git a/plugins/impex/ppm/kis_ppm_export.h b/plugins/impex/ppm/kis_ppm_export.h deleted file mode 100644 index 070db83b7a..0000000000 --- a/plugins/impex/ppm/kis_ppm_export.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2009 Cyrille Berger - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 2 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef _KIS_PPM_EXPORT_H_ -#define _KIS_PPM_EXPORT_H_ - -#include - -#include -#include -#include "ui_kis_wdg_options_ppm.h" - -class KisWdgOptionsPPM : public KisConfigWidget, public Ui::WdgOptionsPPM -{ - Q_OBJECT - -public: - KisWdgOptionsPPM(QWidget *parent) - : KisConfigWidget(parent) - { - setupUi(this); - } - - void setConfiguration(const KisPropertiesConfigurationSP cfg) override; - KisPropertiesConfigurationSP configuration() const override; -}; - - - -class KisPPMExport : public KisImportExportFilter -{ - Q_OBJECT -public: - KisPPMExport(QObject *parent, const QVariantList &); - ~KisPPMExport() override; -public: - KisImportExportErrorCode 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/impex/ppm/kis_ppm_import.cpp b/plugins/impex/ppm/kis_ppm_import.cpp deleted file mode 100644 index ee31b591d3..0000000000 --- a/plugins/impex/ppm/kis_ppm_import.cpp +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (c) 2009 Cyrille Berger - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "kis_ppm_import.h" - -#include - -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "kis_iterator_ng.h" - -K_PLUGIN_FACTORY_WITH_JSON(PPMImportFactory, "krita_ppm_import.json", registerPlugin();) - -KisPPMImport::KisPPMImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) -{ -} - -KisPPMImport::~KisPPMImport() -{ -} - -int readNumber(QIODevice* device) -{ - char c; - int val = 0; - while (true) { - if (!device->getChar(&c)) break; // End of the file - if (isdigit(c)) { - val = 10 * val + c - '0'; - } else if (c == '#') { - device->readLine(); - break; - } else if (isspace((uchar) c)) { - break; - } - } - return val; -} - -class KisPpmFlow -{ -public: - KisPpmFlow() { - } - virtual ~KisPpmFlow() { - } - virtual void nextRow() = 0; - virtual bool valid() = 0; - virtual bool nextUint1() = 0; - virtual quint8 nextUint8() = 0; - virtual quint16 nextUint16() = 0; - -}; - -class KisAsciiPpmFlow : public KisPpmFlow -{ -public: - KisAsciiPpmFlow(QIODevice* device) : m_device(device) { - } - ~KisAsciiPpmFlow() override { - } - void nextRow() override { - } - bool valid() override { - return !m_device->atEnd(); - } - bool nextUint1() override { - return readNumber(m_device) == 1; - } - quint8 nextUint8() override { - return readNumber(m_device); - } - quint16 nextUint16() override { - return readNumber(m_device); - } -private: - QIODevice* m_device; -}; - -class KisBinaryPpmFlow : public KisPpmFlow -{ -public: - KisBinaryPpmFlow(QIODevice* device, int lineWidth) : m_pos(0), m_device(device), m_lineWidth(lineWidth) { - } - ~KisBinaryPpmFlow() override { - } - void nextRow() override { - m_array = m_device->read(m_lineWidth); - m_ptr = m_array.data(); - } - bool valid() override { - return m_array.size() == m_lineWidth; - } - bool nextUint1() override { - if (m_pos == 0) { - m_current = nextUint8(); - m_pos = 8; - } - bool v = (m_current & 1) == 1; - --m_pos; - m_current = m_current >> 1; - return v; - } - quint8 nextUint8() override { - quint8 v = *reinterpret_cast(m_ptr); - m_ptr += 1; - return v; - } - quint16 nextUint16() override { - quint16 v = *reinterpret_cast(m_ptr); - m_ptr += 2; - return qFromBigEndian(v); - } -private: - int m_pos; - quint8 m_current; - char* m_ptr; - QIODevice* m_device; - QByteArray m_array; - int m_lineWidth; -}; - - - -KisImportExportErrorCode KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) -{ - QByteArray array = io->read(2); - - if (array.size() < 2) { - return ImportExportCodes::FileFormatIncorrect; - } - - // Read the type of the ppm file - enum { Puk, P1, P2, P3, P4, P5, P6 } fileType = Puk; // Puk => unknown - - int channels = -1; - bool isAscii = false; - - if (array == "P1") { - fileType = P1; - isAscii = true; - channels = 0; - } else if (array == "P2") { - fileType = P2; - channels = 1; - isAscii = true; - } else if (array == "P3") { - fileType = P3; - channels = 3; - isAscii = true; - } else if (array == "P4") { - fileType = P4; - channels = 0; - } else if (array == "P5") { // PGM - fileType = P5; - channels = 1; - } else if (array == "P6") { // PPM - fileType = P6; - channels = 3; - } - - Q_ASSERT(channels != -1); - char c; io->getChar(&c); - if (!isspace(c)) { - return ImportExportCodes::FileFormatIncorrect;; // Invalid file, it should have a separator now - } - - while (io->peek(1) == "#") { - io->readLine(); - } - - // Read width - int width = readNumber(io); - int height = readNumber(io); - int maxval = 1; - - if (fileType != P1 && fileType != P4) { - maxval = readNumber(io); - } - - dbgFile << "Width = " << width << " height = " << height << "maxval = " << maxval; - - // Select the colorspace depending on the maximum value - int pixelsize = -1; - const KoColorSpace* colorSpace = 0; - - const KoColorProfile *profile = 0; - QString colorSpaceId; - QString bitDepthId; - - - if (maxval <= 255) { - bitDepthId = Integer8BitsColorDepthID.id(); - - if (channels == 1 || channels == 0) { - pixelsize = 1; - colorSpaceId = GrayAColorModelID.id(); - } else { - pixelsize = 3; - colorSpaceId = RGBAColorModelID.id(); - } - } else if (maxval <= 65535) { - bitDepthId = Integer16BitsColorDepthID.id(); - - if (channels == 1 || channels == 0) { - pixelsize = 2; - colorSpaceId = GrayAColorModelID.id(); - } else { - pixelsize = 6; - colorSpaceId = RGBAColorModelID.id(); - } - } else { - dbgFile << "Unknown colorspace"; - return ImportExportCodes::FormatColorSpaceUnsupported; - } - - if (colorSpaceId == RGBAColorModelID.id()) { - profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); - } else if (colorSpaceId == GrayAColorModelID.id()) { - profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); - } - - colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId, bitDepthId, profile); - - KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "built image"); - KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); - - QScopedPointer ppmFlow; - if (isAscii) { - ppmFlow.reset(new KisAsciiPpmFlow(io)); - } else { - ppmFlow.reset(new KisBinaryPpmFlow(io, pixelsize * width)); - } - - for (int v = 0; v < height; ++v) { - KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, v, width); - ppmFlow->nextRow(); - if (!ppmFlow->valid()) { - return ImportExportCodes::FileFormatIncorrect; - } - if (maxval <= 255) { - if (channels == 3) { - do { - KoBgrTraits::setRed(it->rawData(), ppmFlow->nextUint8()); - KoBgrTraits::setGreen(it->rawData(), ppmFlow->nextUint8()); - KoBgrTraits::setBlue(it->rawData(), ppmFlow->nextUint8()); - colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); - } while (it->nextPixel()); - } else if (channels == 1) { - do { - *reinterpret_cast(it->rawData()) = ppmFlow->nextUint8(); - colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); - } while (it->nextPixel()); - } else if (channels == 0) { - do { - if (ppmFlow->nextUint1()) { - *reinterpret_cast(it->rawData()) = 255; - } else { - *reinterpret_cast(it->rawData()) = 0; - } - colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); - } while (it->nextPixel()); - } - } else { - if (channels == 3) { - do { - KoBgrU16Traits::setRed(it->rawData(), ppmFlow->nextUint16()); - KoBgrU16Traits::setGreen(it->rawData(), ppmFlow->nextUint16()); - KoBgrU16Traits::setBlue(it->rawData(), ppmFlow->nextUint16()); - colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); - } while (it->nextPixel()); - } else if (channels == 1) { - do { - *reinterpret_cast(it->rawData()) = ppmFlow->nextUint16(); - colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); - } while (it->nextPixel()); - } - } - } - - image->addNode(layer.data(), image->rootLayer().data()); - - document->setCurrentImage(image); - return ImportExportCodes::OK; -} - -#include "kis_ppm_import.moc" diff --git a/plugins/impex/ppm/kis_ppm_import.h b/plugins/impex/ppm/kis_ppm_import.h deleted file mode 100644 index 07268bff36..0000000000 --- a/plugins/impex/ppm/kis_ppm_import.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2009 Cyrille Berger - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef _KIS_PPM_IMPORT_H_ -#define _KIS_PPM_IMPORT_H_ - -#include -#include - -#include - -class KisDocument; - -class KisPPMImport : public KisImportExportFilter -{ - Q_OBJECT -public: - KisPPMImport(QObject *parent, const QVariantList &); - ~KisPPMImport() override; -public: - KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; -}; - -#endif diff --git a/plugins/impex/ppm/kis_wdg_options_ppm.ui b/plugins/impex/ppm/kis_wdg_options_ppm.ui deleted file mode 100644 index f3e565d5eb..0000000000 --- a/plugins/impex/ppm/kis_wdg_options_ppm.ui +++ /dev/null @@ -1,61 +0,0 @@ - - - WdgOptionsPPM - - - - 0 - 0 - 243 - 94 - - - - - - - Type: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - Binary - - - - - Ascii - - - - - - - - Qt::Vertical - - - - 20 - 46 - - - - - - - - - diff --git a/plugins/impex/ppm/krita_ppm.desktop b/plugins/impex/ppm/krita_ppm.desktop deleted file mode 100644 index dfd659a302..0000000000 --- a/plugins/impex/ppm/krita_ppm.desktop +++ /dev/null @@ -1,76 +0,0 @@ -[Desktop Entry] -Categories=Qt;KDE;Office;Graphics; -Exec=krita %F -GenericName= -Icon=calligrakrita -MimeType=image/x-portable-pixmap;image/x-portable-graymap;image/x-portable-bitmap; -Name=Krita -Name[af]=Krita -Name[ar]=كريتا -Name[bg]=Krita -Name[br]=Krita -Name[bs]=Krita -Name[ca]=Krita -Name[ca@valencia]=Krita -Name[cs]=Krita -Name[cy]=Krita -Name[da]=Krita -Name[de]=Krita -Name[el]=Krita -Name[en_GB]=Krita -Name[eo]=Krita -Name[es]=Krita -Name[et]=Krita -Name[eu]=Krita -Name[fi]=Krita -Name[fr]=Krita -Name[fy]=Krita -Name[ga]=Krita -Name[gl]=Krita -Name[he]=Krita -Name[hi]=केरिता -Name[hne]=केरिता -Name[hr]=Krita -Name[hu]=Krita -Name[ia]=Krita -Name[is]=Krita -Name[it]=Krita -Name[ja]=Krita -Name[kk]=Krita -Name[ko]=Krita -Name[lt]=Krita -Name[lv]=Krita -Name[mr]=क्रिटा -Name[ms]=Krita -Name[nb]=Krita -Name[nds]=Krita -Name[ne]=क्रिता -Name[nl]=Krita -Name[nn]=Krita -Name[pl]=Krita -Name[pt]=Krita -Name[pt_BR]=Krita -Name[ro]=Krita -Name[ru]=Krita -Name[se]=Krita -Name[sk]=Krita -Name[sl]=Krita -Name[sv]=Krita -Name[ta]=கிரிட்டா -Name[tg]=Krita -Name[tr]=Krita -Name[ug]=Krita -Name[uk]=Krita -Name[uz]=Krita -Name[uz@cyrillic]=Krita -Name[wa]=Krita -Name[xh]=Krita -Name[x-test]=xxKritaxx -Name[zh_CN]=Krita -Name[zh_TW]=Krita -NoDisplay=true -StartupNotify=true -Terminal=false -Type=Application -X-KDE-SubstituteUID=false -X-KDE-Username= diff --git a/plugins/impex/ppm/krita_ppm_export.json b/plugins/impex/ppm/krita_ppm_export.json deleted file mode 100644 index ed1c75b990..0000000000 --- a/plugins/impex/ppm/krita_ppm_export.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Icon": "", - "Id": "Krita PPM Export Filter", - "NoDisplay": "true", - "Type": "Service", - "X-KDE-Export": "image/x-portable-pixmap,image/x-portable-graymap,image/x-portable-bitmap", - - "X-KDE-Library": "kritappmexport", - "X-KDE-ServiceTypes": [ - "Krita/FileFilter" - ], - "X-KDE-Weight": "1", - "X-KDE-Extensions" : "ppm" - -} diff --git a/plugins/impex/ppm/krita_ppm_import.json b/plugins/impex/ppm/krita_ppm_import.json deleted file mode 100644 index 92e602588f..0000000000 --- a/plugins/impex/ppm/krita_ppm_import.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Icon": "", - "Id": "Krita PPM Import Filter", - "NoDisplay": "true", - "Type": "Service", - - "X-KDE-Import": "image/x-portable-pixmap,image/x-portable-graymap,image/x-portable-bitmap", - "X-KDE-Library": "kritappmimport", - "X-KDE-ServiceTypes": [ - "Krita/FileFilter" - ], - "X-KDE-Weight": "1", - "X-KDE-Extensions" : "ppm" - -} diff --git a/plugins/impex/ppm/tests/CMakeLists.txt b/plugins/impex/ppm/tests/CMakeLists.txt deleted file mode 100644 index 27bc54a312..0000000000 --- a/plugins/impex/ppm/tests/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) -include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) - -include(KritaAddBrokenUnitTest) - -macro_add_unittest_definitions() - -ecm_add_test(kis_ppm_test.cpp - TEST_NAME kis_ppm_test - LINK_LIBRARIES kritaui Qt5::Test - NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/ppm/tests/data/incorrectFormatFile.txt b/plugins/impex/ppm/tests/data/incorrectFormatFile.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/impex/ppm/tests/data/readonlyFile.txt b/plugins/impex/ppm/tests/data/readonlyFile.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/impex/ppm/tests/data/results/j.pbm.png b/plugins/impex/ppm/tests/data/results/j.pbm.png deleted file mode 100644 index d3a0e385a3..0000000000 Binary files a/plugins/impex/ppm/tests/data/results/j.pbm.png and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/results/test2.pgm.png b/plugins/impex/ppm/tests/data/results/test2.pgm.png deleted file mode 100644 index 9e904b08cd..0000000000 Binary files a/plugins/impex/ppm/tests/data/results/test2.pgm.png and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/results/test2.ppm.png b/plugins/impex/ppm/tests/data/results/test2.ppm.png deleted file mode 100644 index 0e69327c68..0000000000 Binary files a/plugins/impex/ppm/tests/data/results/test2.ppm.png and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/results/test3.pgm.png b/plugins/impex/ppm/tests/data/results/test3.pgm.png deleted file mode 100644 index 8627285c9b..0000000000 Binary files a/plugins/impex/ppm/tests/data/results/test3.pgm.png and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/results/test3.ppm.png b/plugins/impex/ppm/tests/data/results/test3.ppm.png deleted file mode 100644 index e43ae008cb..0000000000 Binary files a/plugins/impex/ppm/tests/data/results/test3.ppm.png and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/sources/j.pbm b/plugins/impex/ppm/tests/data/sources/j.pbm deleted file mode 100644 index a0f832f244..0000000000 --- a/plugins/impex/ppm/tests/data/sources/j.pbm +++ /dev/null @@ -1,12 +0,0 @@ -P1 -6 10 -0 0 0 0 1 0 -0 0 0 0 1 0 -0 0 0 0 1 0 -0 0 0 0 1 0 -0 0 0 0 1 0 -0 0 0 0 1 0 -1 0 0 0 1 0 -0 1 1 1 0 0 -0 0 0 0 0 0 -0 0 0 0 0 0 diff --git a/plugins/impex/ppm/tests/data/sources/test2.pgm b/plugins/impex/ppm/tests/data/sources/test2.pgm deleted file mode 100644 index a5c613deb5..0000000000 Binary files a/plugins/impex/ppm/tests/data/sources/test2.pgm and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/sources/test2.ppm b/plugins/impex/ppm/tests/data/sources/test2.ppm deleted file mode 100644 index d5ca03ca36..0000000000 Binary files a/plugins/impex/ppm/tests/data/sources/test2.ppm and /dev/null differ diff --git a/plugins/impex/ppm/tests/data/sources/test3.pgm b/plugins/impex/ppm/tests/data/sources/test3.pgm deleted file mode 100644 index 78e921ab18..0000000000 --- a/plugins/impex/ppm/tests/data/sources/test3.pgm +++ /dev/null @@ -1,3 +0,0 @@ -P2 -10 10 255 -255 255 255 254 254 254 253 254 254 255 255 254 253 215 113 45 52 149 252 254 255 253 181 33 2 1 1 6 135 252 255 214 50 1 1 0 0 1 31 229 255 187 26 0 0 1 0 1 10 187 255 217 51 2 1 1 1 1 7 161 255 180 17 1 1 1 0 1 9 174 255 129 5 0 0 0 0 1 30 214 255 180 19 1 1 1 1 10 141 248 255 253 174 59 24 27 67 168 248 254 \ No newline at end of file diff --git a/plugins/impex/ppm/tests/data/sources/test3.ppm b/plugins/impex/ppm/tests/data/sources/test3.ppm deleted file mode 100644 index 85c765bd49..0000000000 --- a/plugins/impex/ppm/tests/data/sources/test3.ppm +++ /dev/null @@ -1,3 +0,0 @@ -P3 -10 10 65535 -65535 3 3 65535 9252 3 65535 25956 3 65535 42662 3 62965 58082 3 41891 60394 3 20561 62965 3 3 65535 766 3 65535 21590 3 65535 42148 65535 10278 3 65535 27242 3 65535 44204 3 61423 58082 3 40092 60651 3 18762 63221 3 3 65535 2314 3 65535 23128 3 65535 43947 3 65535 64507 65535 28527 3 65535 45231 3 59880 58339 3 38550 60908 3 17219 63479 3 3 65535 3850 3 65535 24670 3 65535 45489 3 65021 65535 3 44204 65535 65535 46517 3 58339 58595 3 37009 61165 3 15677 63479 3 3 65535 5385 3 65535 26214 3 65535 47031 3 63479 65535 3 42662 65535 3 22101 65535 56797 58852 3 35466 61165 3 14135 63735 3 3 65535 6937 3 65535 27755 3 65535 48572 3 61936 65535 3 41120 65535 3 20561 65535 262 3 65278 33666 61423 3 12338 63992 3 3 65535 8735 3 65535 29299 3 65535 50114 3 60394 65535 3 39577 65535 3 19017 65535 1790 3 65278 23902 3 65021 10790 64250 3 3 65535 10278 3 65535 30841 3 65535 51657 3 58852 65535 3 38036 65535 3 17476 65535 3588 3 65278 25443 3 65021 47545 3 64764 3 65535 11825 3 65535 32382 3 65535 53198 3 57310 65535 3 36494 65535 3 15677 65535 5137 3 65278 27242 3 65021 49087 3 64764 65535 3 58852 3 65535 33925 3 65535 54740 3 55769 65535 3 34952 65535 3 14135 65535 6937 3 65278 28784 3 65021 50885 3 64507 65535 3 57053 65535 3 35209 3 65535 56283 3 54226 65535 3 33410 65535 3 12594 65535 8481 3 65278 30584 3 65021 52428 3 64507 65535 3 55511 65535 3 33410 65535 3 11567 \ No newline at end of file diff --git a/plugins/impex/ppm/tests/data/writeonlyFile.txt b/plugins/impex/ppm/tests/data/writeonlyFile.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/ppm/tests/kis_ppm_test.cpp deleted file mode 100644 index fce6ee9d5c..0000000000 --- a/plugins/impex/ppm/tests/kis_ppm_test.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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_ppm_test.h" - - -#include -#include - -#include - -#include "filestest.h" - -#ifndef FILES_DATA_DIR -#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" -#endif - - -const QString PPMMimetype = "image/x-portable-pixmap"; - -void KisPPMTest::testFiles() -{ - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); -} - - -void KisPPMTest::testImportFromWriteonly() -{ - TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PPMMimetype); -} - - -void KisPPMTest::testExportToReadonly() -{ - TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PPMMimetype); -} - - -void KisPPMTest::testImportIncorrectFormat() -{ - TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PPMMimetype); -} - - -KISTEST_MAIN(KisPPMTest) - diff --git a/plugins/impex/qimageio/kis_qimageio_import.cpp b/plugins/impex/qimageio/kis_qimageio_import.cpp index 411162e8a4..a73e6f3e66 100644 --- a/plugins/impex/qimageio/kis_qimageio_import.cpp +++ b/plugins/impex/qimageio/kis_qimageio_import.cpp @@ -1,73 +1,67 @@ /* * 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_qimageio_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOImportFactory, "krita_qimageio_import.json", registerPlugin();) KisQImageIOImport::KisQImageIOImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOImport::~KisQImageIOImport() { } KisImportExportErrorCode KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { - QFileInfo fi(filename()); + QImage img; - if (!img.loadFromData(io->readAll(), fi.suffix().toLower().toLatin1())) { + if (!img.loadFromData(io->readAll()/*, fi.suffix().toLower().toLatin1()*/)) { return ImportExportCodes::FileFormatIncorrect; } - const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); - KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, i18n("Imported Image")); - - KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); - layer->paintDevice()->convertFromQImage(img, 0, 0, 0); - image->addNode(layer.data(), image->rootLayer().data()); - + KisImageSP image = KisImage::fromQImage(img, document->createUndoStore()); document->setCurrentImage(image); return ImportExportCodes::OK; } #include "kis_qimageio_import.moc" diff --git a/plugins/impex/qimageio/krita_qimageio.desktop b/plugins/impex/qimageio/krita_qimageio.desktop index 96cdbfa8e9..87f2c9b15a 100644 --- a/plugins/impex/qimageio/krita_qimageio.desktop +++ b/plugins/impex/qimageio/krita_qimageio.desktop @@ -1,72 +1,72 @@ [Desktop Entry] Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[nn]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita Exec=krita %F -MimeType=image/bmp;image/x-xpixmap;image/x-xbitmap;image/webp;image/vnd.microsoft.icon; +MimeType=image/bmp;image/x-xpixmap;image/x-xbitmap;image/webp;image/vnd.microsoft.icon;image/x-portable-pixmap;image/x-portable-graymap;image/x-portable-bitmap; Type=Application Icon=calligrakrita Categories=Qt;KDE;Office;Graphics; StartupNotify=true NoDisplay=true diff --git a/plugins/impex/qimageio/krita_qimageio_export.json b/plugins/impex/qimageio/krita_qimageio_export.json index df8877c9e8..b7009943ef 100644 --- a/plugins/impex/qimageio/krita_qimageio_export.json +++ b/plugins/impex/qimageio/krita_qimageio_export.json @@ -1,12 +1,12 @@ { "Id": "Krita QImageIO Export Filter", "NoDisplay": "true", "Type": "Service", - "X-KDE-Export": "image/bmp,image/x-xpixmap,image/x-xbitmap,image/vnd.microsoft.icon,image/webp", + "X-KDE-Export": "image/bmp,image/x-xpixmap,image/x-xbitmap,image/webp,image/vnd.microsoft.icon,image/x-portable-pixmap,image/x-portable-graymap,image/x-portable-bitmap", "X-KDE-Library": "kritaqimageioexport", "X-KDE-ServiceTypes": [ "Krita/FileFilter" ], "X-KDE-Weight": "1", - "X-KDE-Extensions" : "bmp,xpm,xbm,ico,webp" + "X-KDE-Extensions" : "bmp,xpm,xbm,ico,webp,ppm" } diff --git a/plugins/impex/qimageio/krita_qimageio_import.json b/plugins/impex/qimageio/krita_qimageio_import.json index 3893ad749e..9fe6cd908f 100644 --- a/plugins/impex/qimageio/krita_qimageio_import.json +++ b/plugins/impex/qimageio/krita_qimageio_import.json @@ -1,12 +1,12 @@ { "Id": "Krita QImageIO Import Filter", "NoDisplay": "true", "Type": "Service", - "X-KDE-Import": "image/bmp,image/x-xpixmap,image/x-xbitmap,image/webp,image/vnd.microsoft.icon", + "X-KDE-Import": "image/bmp,image/x-xpixmap,image/x-xbitmap,image/webp,image/vnd.microsoft.icon,image/x-portable-pixmap,image/x-portable-graymap,image/x-portable-bitmap", "X-KDE-Library": "kritaqimageioimport", "X-KDE-ServiceTypes": [ "Krita/FileFilter" ], "X-KDE-Weight": "1", - "X-KDE-Extensions" : "bmp,webp,xbm,ico" + "X-KDE-Extensions" : "bmp,webp,xbm,ico,ppm" } diff --git a/plugins/impex/qml/qml_converter.cc b/plugins/impex/qml/qml_converter.cc index 48b9440982..010693d2ba 100644 --- a/plugins/impex/qml/qml_converter.cc +++ b/plugins/impex/qml/qml_converter.cc @@ -1,97 +1,97 @@ /* * Copyright (c) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qml_converter.h" #include #include #include #include #define SPACE " " QMLConverter::QMLConverter() { } QMLConverter::~QMLConverter() { } KisImportExportErrorCode QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image) { QTextStream out(io); out.setCodec("UTF-8"); out << "import QtQuick 1.1" << "\n\n"; out << "Rectangle {\n"; writeInt(out, 1, "width", image->width()); writeInt(out, 1, "height", image->height()); out << "\n"; QFileInfo info(filename); QFileInfo infoRealFile(realFilename); KisNodeSP node = image->rootLayer()->firstChild(); - QString imageDir = infoRealFile.baseName() + "_images"; + QString imageDir = infoRealFile.completeBaseName() + "_images"; QString imagePath = infoRealFile.absolutePath() + '/' + imageDir; if (node) { QDir dir; bool success = dir.mkpath(imagePath); if (!success) { return ImportExportCodes::CannotCreateFile; } } dbgFile << "Saving images to " << imagePath; while(node) { KisPaintDeviceSP projection = node->projection(); QRect rect = projection->exactBounds(); QImage qmlImage = projection->convertToQImage(0, rect.x(), rect.y(), rect.width(), rect.height()); QString name = node->name().replace(' ', '_').toLower(); QString fileName = name + ".png"; qmlImage.save(imagePath +'/'+ fileName); out << SPACE << "Image {\n"; writeString(out, 2, "id", name); writeInt(out, 2, "x", rect.x()); writeInt(out, 2, "y", rect.y()); writeInt(out, 2, "width", rect.width()); writeInt(out, 2, "height", rect.height()); writeString(out, 2, "source", "\"" + imageDir + '/' + fileName + "\"" ); writeString(out, 2, "opacity", QString().setNum(node->opacity()/255.0)); out << SPACE << "}\n"; node = node->nextSibling(); } out << "}\n"; return ImportExportCodes::OK; } void QMLConverter::writeString(QTextStream& out, int spacing, const QString& setting, const QString& value) { for (int space = 0; space < spacing; space++) { out << SPACE; } out << setting << ": " << value << "\n"; } void QMLConverter::writeInt(QTextStream& out, int spacing, const QString& setting, int value) { writeString(out, spacing, setting, QString::number(value)); } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp index b869f6ddd3..2ff49314fc 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp @@ -1,196 +1,196 @@ /** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2008-08-21 * @brief a combo box with a width not depending of text * content size * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008 by Andi Clemens * andi dot clemens at googlemail dot com * @author Copyright (C) 2005 by Tom Albers * tomalbers at kde dot nl * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "squeezedcombobox.h" // Qt includes #include #include #include #include #include namespace KDcrawIface { class Q_DECL_HIDDEN SqueezedComboBox::Private { public: Private() { timer = 0; } QMap originalItems; QTimer* timer; }; SqueezedComboBox::SqueezedComboBox(QWidget* const parent, const char* name) : QComboBox(parent), d(new Private) { setObjectName(name); setMinimumWidth(100); d->timer = new QTimer(this); d->timer->setSingleShot(true); connect(d->timer, &QTimer::timeout, this, &SqueezedComboBox::slotTimeOut); connect(this, static_cast(&SqueezedComboBox::activated), this, &SqueezedComboBox::slotUpdateToolTip); } SqueezedComboBox::~SqueezedComboBox() { d->originalItems.clear(); delete d->timer; delete d; } bool SqueezedComboBox::contains(const QString& text) const { if (text.isEmpty()) return false; for (QMap::const_iterator it = d->originalItems.constBegin() ; it != d->originalItems.constEnd(); ++it) { if (it.value() == text) return true; } return false; } QSize SqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18; int maxH = qMax( fm.lineSpacing(), 14 ) + 2; QStyleOptionComboBox options; options.initFrom(this); return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); } void SqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, const QVariant& userData) { d->originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem), userData); // if this is the first item, set the tooltip. if (index == 0) slotUpdateToolTip(0); } void SqueezedComboBox::insertSqueezedList(const QStringList& newItems, int index) { for(QStringList::const_iterator it = newItems.constBegin() ; it != newItems.constEnd() ; ++it) { insertSqueezedItem(*it, index); index++; } } void SqueezedComboBox::addSqueezedItem(const QString& newItem, const QVariant& userData) { insertSqueezedItem(newItem, count(), userData); } void SqueezedComboBox::setCurrent(const QString& itemText) { QString squeezedText = squeezeText(itemText); qint32 itemIndex = findText(squeezedText); if (itemIndex >= 0) setCurrentIndex(itemIndex); } void SqueezedComboBox::resizeEvent(QResizeEvent *) { d->timer->start(200); } void SqueezedComboBox::slotTimeOut() { for (QMap::iterator it = d->originalItems.begin() ; it != d->originalItems.end(); ++it) { setItemText( it.key(), squeezeText( it.value() ) ); } } QString SqueezedComboBox::squeezeText(const QString& original) const { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = width()-30; QFontMetrics fm( fontMetrics() ); // If we can fit the full text, return that. if (fm.width(original) < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize-fm.width("..."); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.width(original.right(i)) > widgetSize) { sqItem = QString(original.left(i) + "..."); break; } } return sqItem; } void SqueezedComboBox::slotUpdateToolTip(int index) { setToolTip(d->originalItems[index]); } -QString SqueezedComboBox::itemHighlighted() const +QString SqueezedComboBox::currentUnsqueezedText() const { int curItem = currentIndex(); return d->originalItems[curItem]; } QString SqueezedComboBox::item(int index) const { return d->originalItems[index]; } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h index 9aadc9e97d..7a482e52c2 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h @@ -1,165 +1,165 @@ /** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2008-08-21 * @brief a combo box with a width not depending of text * content size * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008 by Andi Clemens * andi dot clemens at googlemail dot com * @author Copyright (C) 2005 by Tom Albers * tomalbers at kde dot nl * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef SQUEEZEDCOMBOBOX_H #define SQUEEZEDCOMBOBOX_H // Qt includes #include // Local includes namespace KDcrawIface { /** @class SqueezedComboBox * * This widget is a QComboBox, but then a little bit * different. It only shows the right part of the items * depending on de size of the widget. When it is not * possible to show the complete item, it will be shortened * and "..." will be prepended. */ class SqueezedComboBox : public QComboBox { Q_OBJECT public: /** * Constructor * @param parent parent widget * @param name name to give to the widget */ explicit SqueezedComboBox(QWidget* const parent = 0, const char* name = 0 ); /** * destructor */ ~SqueezedComboBox() override; /** * * Returns true if the combobox contains the original (not-squeezed) * version of text. * @param text the original (not-squeezed) text to check for */ bool contains(const QString& text) const; /** * This inserts a item to the list. See QComboBox::insertItem() * for details. Please do not use QComboBox::insertItem() to this * widget, as that will fail. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param index the position in the widget. * @param userData custom meta-data assigned to new item. */ void insertSqueezedItem(const QString& newItem, int index, const QVariant& userData=QVariant()); /** * This inserts items to the list. See QComboBox::insertItems() * for details. Please do not use QComboBox:: insertItems() to this * widget, as that will fail. * @param newItems the originals (long version) of the items which needs * to be added to the combobox * @param index the position in the widget. */ void insertSqueezedList(const QStringList& newItems, int index); /** * Append an item. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param userData custom meta-data assigned to new item. */ void addSqueezedItem(const QString& newItem, const QVariant& userData=QVariant()); /** * Set the current item to the one matching the given text. * * @param itemText the original (long version) of the item text */ void setCurrent(const QString& itemText); /** * This method returns the full text (not squeezed) of the currently * highlighted item. * @return full text of the highlighted item */ - QString itemHighlighted() const; + QString currentUnsqueezedText() const; /** * This method returns the full text (not squeezed) for the index. * @param index the position in the widget. * @return full text of the item */ QString item(int index) const; /** * Sets the sizeHint() of this widget. */ QSize sizeHint() const override; private Q_SLOTS: void slotTimeOut(); void slotUpdateToolTip(int index); private: void resizeEvent(QResizeEvent*) override; QString squeezeText(const QString& original) const; // Prevent these from being used. QString currentText() const; void setCurrentText(const QString& itemText); void insertItem(const QString& text); void insertItem(qint32 index, const QString& text); void insertItem(int index, const QIcon& icon, const QString& text, const QVariant& userData=QVariant()); void insertItems(int index, const QStringList& list); void addItem(const QString& text); void addItem(const QIcon& icon, const QString& text, const QVariant& userData=QVariant()); void addItems(const QStringList& texts); QString itemText(int index) const; private: class Private; Private* const d; }; } // namespace KDcrawIface #endif // SQUEEZEDCOMBOBOX_H diff --git a/plugins/impex/spriter/kis_spriter_export.cpp b/plugins/impex/spriter/kis_spriter_export.cpp index c5cc2308ad..1ec22ed5ad 100644 --- a/plugins/impex/spriter/kis_spriter_export.cpp +++ b/plugins/impex/spriter/kis_spriter_export.cpp @@ -1,651 +1,651 @@ /* * 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_spriter_export.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 // for KisDegreesToRadians #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisSpriterExportFactory, "krita_spriter_export.json", registerPlugin();) KisSpriterExport::KisSpriterExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSpriterExport::~KisSpriterExport() { } KisImportExportErrorCode KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName) { QFileInfo fi(fileName); QDir d = fi.absoluteDir(); d.mkpath(d.path()); QRect rc = m_image->bounds().intersected(dev->exactBounds()); if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { dev = new KisPaintDevice(*dev.data()); dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.forceSRGB = true; vKisAnnotationSP_it beginIt = m_image->beginAnnotations(); vKisAnnotationSP_it endIt = m_image->endAnnotations(); KisPNGConverter converter(0); KisImportExportErrorCode res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0); return res; } KisImportExportErrorCode KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId) { // qDebug() << "parseFolder: parent" << parentGroup->name() // << "folderName" << folderName // << "basepath" << basePath; int currentFolder=0; if(folderId == 0) { folderId = ¤tFolder; } QString pathName; if (!folderName.isEmpty()) { pathName = folderName + "/"; } KisNodeSP child = parentGroup->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { KisImportExportErrorCode res = parseFolder(qobject_cast(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId); if (!res.isOk()) { return res; } } child = child->prevSibling(); } Folder folder; folder.id = *folderId; folder.name = folderName; folder.groupName = parentGroup->name(); int fileId = 0; child = parentGroup->lastChild(); while (child) { if (child->visible() && !child->inherits("KisGroupLayer") && !child->inherits("KisMask")) { QRectF rc = m_image->bounds().intersected(child->exactBounds()); QString layerBaseName = child->name().split(" ").first(); SpriterFile file; file.id = fileId++; file.pathName = pathName; file.baseName = layerBaseName; file.layerName = child->name(); file.name = folderName + "/" + layerBaseName + ".png"; qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); file.width = xmax - xmin; file.height = ymax - ymin; file.x = xmin; file.y = ymin; //qDebug() << "Created file" << file.id << file.name << file.pathName << file.baseName << file.width << file.height << file.layerName; KisImportExportErrorCode result = savePaintDevice(child->projection(), basePath + file.name); if (result.isOk()) { folder.files.append(file); } else { return result; } } child = child->prevSibling(); } if (folder.files.size() > 0) { //qDebug() << "Adding folder" << folder.id << folder.name << folder.groupName << folder.files.length(); m_folders.append(folder); (*folderId)++; } return ImportExportCodes::OK; } Bone *KisSpriterExport::parseBone(const Bone *parent, KisGroupLayerSP groupLayer) { static int boneId = 0; QString groupBaseName = groupLayer->name().split(" ").first(); Bone *bone = new Bone; bone->id = boneId++; bone->parentBone = parent; bone->name = groupBaseName; if (m_boneLayer) { QRectF rc = m_image->bounds().intersected(m_boneLayer->exactBounds()); qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); bone->x = (xmin + xmax) / 2; bone->y = -(ymin + ymax) / 2; bone->width = xmax - xmin; bone->height = ymax - ymin; } else { bone->x = 0.0; bone->y = 0.0; bone->width = 0.0; bone->height = 0.0; } if (parent) { bone->localX = bone->x - parent->x; bone->localY = bone->y - parent->y; } else { bone->localX = bone->x; bone->localY = bone->y; } bone->localAngle = 0.0; bone->localScaleX = 1.0; bone->localScaleY = 1.0; KisNodeSP child = groupLayer->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { bone->bones.append(parseBone(bone, qobject_cast(child.data()))); } child = child->prevSibling(); } //qDebug() << "Created bone" << bone->id << "with" << bone->bones.size() << "bones"; return bone; } void copyBone(Bone *startBone) { startBone->fixLocalX = startBone->localX; startBone->fixLocalY = startBone->localY; startBone->fixLocalAngle = startBone->localAngle; startBone->fixLocalScaleX= startBone->localScaleX; startBone->fixLocalScaleY= startBone->localScaleY; Q_FOREACH(Bone *child, startBone->bones) { copyBone(child); } } void KisSpriterExport::fixBone(Bone *bone) { qreal boneLocalAngle = 0; qreal boneLocalScaleX = 1; if (bone->bones.length() >= 1) { // if a bone has one or more children, point at first child Bone *childBone = bone->bones[0]; qreal dx = childBone->x - bone->x; qreal dy = childBone->y - bone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } else if (bone->parentBone) { // else, if bone has parent, point away from parent qreal dx = bone->x - bone->parentBone->x; qreal dy = bone->y - bone->parentBone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } // adjust bone angle bone->fixLocalAngle += boneLocalAngle; bone->fixLocalScaleX *= boneLocalScaleX; // rotate all the child bones back to world position for (int i = 0; i < bone->bones.length(); ++i) { Bone *childBone = bone->bones[i]; qreal tx = childBone->fixLocalX; qreal ty = childBone->fixLocalY; childBone->fixLocalX = tx * cos(-boneLocalAngle) - ty * sin(-boneLocalAngle); childBone->fixLocalY = tx * sin(-boneLocalAngle) + ty * cos(-boneLocalAngle); childBone->fixLocalX /= boneLocalScaleX; childBone->fixLocalAngle -= boneLocalAngle; childBone->fixLocalScaleX /= boneLocalScaleX; } // rotate all the child objects back to world position for (int i = 0; i < m_objects.length(); ++i) { if (m_objects[i].bone == bone) { m_objects[i].fixLocalAngle -= boneLocalAngle; m_objects[i].fixLocalScaleX /= boneLocalScaleX; } } // process all child bones for (int i = 0; i < bone->bones.length(); ++i) { fixBone(bone->bones[i]); } } void KisSpriterExport::writeBoneRef(const Bone *bone, QDomElement &key, QDomDocument &scml) { if (!bone) return; QDomElement boneRef = scml.createElement("bone_ref"); key.appendChild(boneRef); boneRef.setAttribute("id", bone->id); if (bone->parentBone) { boneRef.setAttribute("parent", bone->parentBone->id); } boneRef.setAttribute("timeline", m_timelineid++); boneRef.setAttribute("key", "0"); Q_FOREACH(const Bone *childBone, bone->bones) { writeBoneRef(childBone, key, scml); } } void KisSpriterExport::writeBone(const Bone *bone, QDomElement &animation, QDomDocument &scml) { if (!bone) return; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid); timeline.setAttribute("name", bone->name); timeline.setAttribute("object_type", "bone"); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", 0); QDomElement boneEl = scml.createElement("bone"); key.appendChild(boneEl); boneEl.setAttribute("x", QString::number(bone->fixLocalX, 'f', 2)); boneEl.setAttribute("y", QString::number(bone->fixLocalY, 'f', 2)); boneEl.setAttribute("angle", QString::number(bone->fixLocalAngle, 'f', 2)); boneEl.setAttribute("scale_x", QString::number(bone->fixLocalScaleX, 'f', 2)); boneEl.setAttribute("scale_y", QString::number(bone->fixLocalScaleY, 'f', 2)); m_timelineid++; Q_FOREACH(const Bone *childBone, bone->bones) { writeBone(childBone, animation, scml); } } void KisSpriterExport::fillScml(QDomDocument &scml, const QString &entityName) { //qDebug() << "Creating scml" << entityName; QDomElement root = scml.createElement("spriter_data"); scml.appendChild(root); root.setAttribute("scml_version", 1); root.setAttribute("generator", "krita"); root.setAttribute("generator_version", qApp->applicationVersion()); Q_FOREACH(const Folder &folder, m_folders) { QDomElement fe = scml.createElement("folder"); root.appendChild(fe); fe.setAttribute("id", folder.id); fe.setAttribute("name", folder.name); Q_FOREACH(const SpriterFile &file, folder.files) { QDomElement fileElement = scml.createElement("file"); fe.appendChild(fileElement); fileElement.setAttribute("id", file.id); fileElement.setAttribute("name", file.name); fileElement.setAttribute("width", QString::number(file.width, 'f', 2)); fileElement.setAttribute("height", QString::number(file.height, 'f', 2)); // qreal pivotX=0; // qreal pivotY=1; // Q_FOREACH(const SpriterObject &object, m_objects) { // if(file.id == object.fileId) // { // pivotX = (0.0 -(object.fixLocalX / file.width)); // pivotY = (1.0 -(object.fixLocalY / file.height)); // break; // } // } // fileElement.setAttribute("pivot_x", QString::number(pivotX, 'f', 2)); // fileElement.setAttribute("pivot_y", QString::number(pivotY, 'f', 2)); } } // entity QDomElement entity = scml.createElement("entity"); root.appendChild(entity); entity.setAttribute("id", "0"); entity.setAttribute("name", entityName); // entity/animation QDomElement animation = scml.createElement("animation"); entity.appendChild(animation); animation.setAttribute("id", "0"); animation.setAttribute("name", "default"); animation.setAttribute("length", "1000"); animation.setAttribute("looping", "false"); // entity/animation/mainline QDomElement mainline = scml.createElement("mainline"); animation.appendChild(mainline); QDomElement key = scml.createElement("key"); mainline.appendChild(key); key.setAttribute("id", "0"); m_timelineid = 0; writeBoneRef(m_rootBone, key, scml); Q_FOREACH(const SpriterObject &object, m_objects) { QDomElement oe = scml.createElement("object_ref"); key.appendChild(oe); oe.setAttribute("id", object.id); if (object.bone) { oe.setAttribute("parent", object.bone->id); } oe.setAttribute("timeline", m_timelineid++); oe.setAttribute("key", "0"); oe.setAttribute("z_index", object.id); } // entity/animation/timeline m_timelineid = 0; if (m_rootBone) { writeBone(m_rootBone, animation, scml); } Q_FOREACH(const SpriterObject &object, m_objects) { Folder folder; Q_FOREACH(const Folder & f, m_folders) { if (f.id == object.folderId) { folder = f; break; } } SpriterFile file; file.id = -1; Q_FOREACH(const SpriterFile &f, folder.files) { if (f.id == object.fileId) { file = f; break; } } Q_ASSERT(file.id >= 0); QString objectName = "object-" + file.baseName; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid++); timeline.setAttribute("name", objectName); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", "0"); QDomElement objectEl = scml.createElement("object"); key.appendChild(objectEl); objectEl.setAttribute("folder", object.folderId); objectEl.setAttribute("file", object.fileId); objectEl.setAttribute("x", object.fixLocalX); objectEl.setAttribute("y", object.fixLocalY); objectEl.setAttribute("angle", QString::number(kisRadiansToDegrees(object.fixLocalAngle), 'f', 2)); objectEl.setAttribute("scale_x", QString::number(object.fixLocalScaleX, 'f', 2)); objectEl.setAttribute("scale_y", QString::number(object.fixLocalScaleY, 'f', 2)); } } Bone *findBoneByName(Bone *startBone, const QString &name) { if (!startBone) return 0; //qDebug() << "findBoneByName" << name << "starting with" << startBone->name; if (startBone->name == name) { return startBone; } Q_FOREACH(Bone *child, startBone->bones) { //qDebug() << "looking for" << name << "found" << child->name; if (child->name == name) { return child; } Bone *grandChild = findBoneByName(child, name); if (grandChild){ return grandChild; } } return 0; } KisImportExportErrorCode KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QFileInfo fi(filename()); m_image = document->savingImage(); if (m_image->rootLayer()->childCount() == 0) { return ImportExportCodes::Failure; } KisGroupLayerSP root = m_image->rootLayer(); m_boneLayer = qobject_cast(root->findChildByName("bone").data()); //qDebug() << "Found boneLayer" << m_boneLayer; m_rootLayer= qobject_cast(root->findChildByName("root").data()); //qDebug() << "Fond rootLayer" << m_rootLayer; KisImportExportErrorCode result = parseFolder(m_image->rootLayer(), "", fi.absolutePath()); if (!result.isOk()) { dbgFile << "There were errors encountered while using the spriter exporter."; return result; } m_rootBone = 0; if (m_rootLayer) { m_rootBone = parseBone(0, m_rootLayer); } // Generate objects int objectId = 0; for (int folderIndex = 0, folderCount = m_folders.size(); folderIndex < folderCount; ++folderIndex) { Folder folder = m_folders[folderCount - 1 - folderIndex]; for (int fileIndex = 0, fileCount = folder.files.size(); fileIndex < fileCount; ++ fileIndex) { SpriterFile file = folder.files[fileCount - 1 - fileIndex]; SpriterObject spriterObject; spriterObject.id = objectId++; spriterObject.folderId = folder.id; spriterObject.fileId = file.id; spriterObject.x = file.x; spriterObject.y = -file.y; Bone *bone = 0; //qDebug() << "file layername" << file.layerName; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("bone(")) { int start = file.layerName.indexOf("bone(") + 5; int end = file.layerName.indexOf(')', start); QString boneName = file.layerName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // layer.name format: "base_name" if (!bone && m_rootBone) { bone = findBoneByName(m_rootBone, file.layerName); } // group.name format: "base_name bone(bone_name)" if (!bone && m_rootBone) { if (folder.groupName.contains("bone(")) { int start = folder.groupName.indexOf("bone(") + 5; int end = folder.groupName.indexOf(')', start); QString boneName = folder.groupName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // group.name format: "base_name" if (!bone) { bone = findBoneByName(m_rootBone, folder.groupName); } } if (!bone) { bone = m_rootBone; } if (bone) { spriterObject.bone = bone; spriterObject.localX = spriterObject.x - bone->x; spriterObject.localY = spriterObject.y - bone->y; } else { spriterObject.bone = 0; spriterObject.localX = spriterObject.x; spriterObject.localY = spriterObject.y; } spriterObject.localAngle = 0; spriterObject.localScaleX = 1.0; spriterObject.localScaleY = 1.0; SpriterSlot *slot = 0; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("slot(")) { int start = file.layerName.indexOf("slot(") + 5; int end = file.layerName.indexOf(')', start); slot = new SpriterSlot(); slot->name = file.layerName.mid(start, end - start); slot->defaultAttachmentFlag = file.layerName.contains("*"); } spriterObject.slot = slot; // qDebug() << "Created object" << spriterObject.id << spriterObject.folderId // << spriterObject.fileId << spriterObject.x << spriterObject.y // << spriterObject.localX << spriterObject.localY; m_objects.append(spriterObject); } } // Copy object transforms for (int i = 0; i < m_objects.size(); ++i) { m_objects[i].fixLocalX = m_objects[i].localX; m_objects[i].fixLocalY = m_objects[i].localY; m_objects[i].fixLocalAngle = m_objects[i].localAngle; m_objects[i].fixLocalScaleX = m_objects[i].localScaleX; m_objects[i].fixLocalScaleY = m_objects[i].localScaleY; } // Calculate bone angles if (m_rootBone) { copyBone(m_rootBone); fixBone(m_rootBone); } // Generate scml QDomDocument scml; - fillScml(scml, fi.baseName()); + fillScml(scml, fi.completeBaseName()); bool openedHere = false; if (!io->isOpen()) { openedHere = io->open(QIODevice::WriteOnly); if (!openedHere) { // unsuccessful open return ImportExportCodes::NoAccessToWrite; } } QString towrite = "\n"; if (io->write(towrite.toUtf8()) != towrite.length()) { return ImportExportCodes::ErrorWhileWriting; } towrite = scml.toString(4).toUtf8(); if (io->write(towrite.toUtf8()) != towrite.length()) { return ImportExportCodes::ErrorWhileWriting; } delete m_rootBone; if (openedHere) { // FIXME: casues crash... //io->close(); } return ImportExportCodes::OK; } void KisSpriterExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Spriter"); } #include "kis_spriter_export.moc" diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index fd5d079426..b32692d6f9 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,201 +1,202 @@ /* * 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_tiff_test.h" #include #include #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.h" #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif const QString TiffMimetype = "image/tiff"; void KisTiffTest::testFiles() { // XXX: make the exiv io backends real plugins KisExiv2::initialize(); QStringList excludes; #ifndef CPU_32_BITS excludes << "flower-minisblack-06.tif"; #endif #ifdef HAVE_LCMS2 excludes << "flower-separated-contig-08.tif" << "flower-separated-contig-16.tif" << "flower-separated-planar-08.tif" << "flower-separated-planar-16.tif" << "flower-minisblack-02.tif" << "flower-minisblack-04.tif" << "flower-minisblack-08.tif" << "flower-minisblack-10.tif" << "flower-minisblack-12.tif" << "flower-minisblack-14.tif" << "flower-minisblack-16.tif" << "flower-minisblack-24.tif" << "flower-minisblack-32.tif" << "jim___dg.tif" << "jim___gg.tif" << "strike.tif"; #endif excludes << "text.tif" << "ycbcr-cat.tif"; TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes, QString(), 1); } void KisTiffTest::testRoundTripRGBF16() { // Disabled for now, it's broken because we assumed integers. #if 0 QRect testRect(0,0,1000,1000); QRect fillRect(100,100,100,100); const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0); KisDocument *doc0 = qobject_cast(KisPart::instance()->createDocument()); doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0); QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff")); tmpFile.open(); doc0->setBackupFile(false); doc0->setOutputMimeType("image/tiff"); doc0->setFileBatchMode(true); doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName())); KisNodeSP layer0 = doc0->image()->root()->firstChild(); Q_ASSERT(layer0); layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16)); KisDocument *doc1 = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc1); doc1->setFileBatchMode(false); KisImportExportErrorCode status; QString s = manager.importDocument(tmpFile.fileName(), QString(), status); dbgKrita << s; Q_ASSERT(doc1->image()); QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect); QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect); QCOMPARE(ref0, ref1); #endif } void KisTiffTest::testSaveTiffColorSpace(QString colorModel, QString colorDepth, QString colorProfile) { - KoColorSpaceRegistry registry; const KoColorSpace *space = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); - TestUtil::testExportToColorSpace(QString(FILES_DATA_DIR), TiffMimetype, space, ImportExportCodes::OK, true); + if (space) { + TestUtil::testExportToColorSpace(QString(FILES_DATA_DIR), TiffMimetype, space, ImportExportCodes::OK, true); + } } void KisTiffTest::testSaveTiffRgbaColorSpace() { QString profile = "sRGB-elle-V2-srgbtrc"; testSaveTiffColorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); profile = "sRGB-elle-V2-g10"; testSaveTiffColorSpace(RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); } void KisTiffTest::testSaveTiffGreyAColorSpace() { QString profile = "Gray-D50-elle-V2-srgbtrc"; testSaveTiffColorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); profile = "Gray-D50-elle-V2-g10"; testSaveTiffColorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(GrayAColorModelID.id(), Float16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), profile); } void KisTiffTest::testSaveTiffCmykColorSpace() { QString profile = "Chemical proof"; testSaveTiffColorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); testSaveTiffColorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); } void KisTiffTest::testSaveTiffLabColorSpace() { const QString profile = "Lab identity build-in"; testSaveTiffColorSpace(LABAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); testSaveTiffColorSpace(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), profile); } void KisTiffTest::testSaveTiffYCrCbAColorSpace() { /* * There is no public/open profile for YCrCbA, so no way to test it... * const QString profile = ""; testSaveTiffColorSpace(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id(), profile); testSaveTiffColorSpace(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id(), profile); testSaveTiffColorSpace(YCbCrAColorModelID.id(), Float32BitsColorDepthID.id(), profile); */ } void KisTiffTest::testImportFromWriteonly() { TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TiffMimetype); } void KisTiffTest::testExportToReadonly() { TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TiffMimetype, true); } void KisTiffTest::testImportIncorrectFormat() { TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TiffMimetype); } KISTEST_MAIN(KisTiffTest) diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp index 4008b502cc..2927af5fae 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -1,167 +1,172 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hairy_paintop.h" #include "kis_hairy_paintop_settings.h" #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include #include #include #include #include #include #include #include #include "kis_brush.h" KisHairyPaintOp::KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image) Q_ASSERT(settings); m_dev = node ? node->paintDevice() : 0; KisBrushOptionProperties brushOption; brushOption.readOptionSetting(settings); KisBrushSP brush = brushOption.brush(); KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace()); // properly initialize fake paint information to avoid warnings KisPaintInformation fakePaintInformation; fakePaintInformation.setRandomSource(new KisRandomSource()); fakePaintInformation.setPerStrokeRandomSource(new KisPerStrokeRandomSource()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), fakePaintInformation); } else { brush->mask(dab, painter->paintColor(), KisDabShape(), fakePaintInformation); } m_brush.fromDabWithDensity(dab, settings->getDouble(HAIRY_BRISTLE_DENSITY) * 0.01); m_brush.setInkColor(painter->paintColor()); loadSettings(static_cast(settings.data())); m_brush.setProperties(&m_properties); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); + + bool mirrorXAxis = settings->getBool("runtimeCanvasMirroredX", false); + bool mirrorYAxis = settings->getBool("runtimeCanvasMirroredY", false); + m_mirrorFlip = mirrorXAxis != mirrorYAxis; } + void KisHairyPaintOp::loadSettings(const KisBrushBasedPaintOpSettings *settings) { m_properties.inkAmount = settings->getInt(HAIRY_INK_AMOUNT); //TODO: wait for the transfer function with variable size m_properties.inkDepletionCurve = settings->getCubicCurve(HAIRY_INK_DEPLETION_CURVE).floatTransfer(m_properties.inkAmount); m_properties.inkDepletionEnabled = settings->getBool(HAIRY_INK_DEPLETION_ENABLED); m_properties.useSaturation = settings->getBool(HAIRY_INK_USE_SATURATION); m_properties.useOpacity = settings->getBool(HAIRY_INK_USE_OPACITY); m_properties.useWeights = settings->getBool(HAIRY_INK_USE_WEIGHTS); m_properties.pressureWeight = settings->getDouble(HAIRY_INK_PRESSURE_WEIGHT) / 100.0; m_properties.bristleLengthWeight = settings->getDouble(HAIRY_INK_BRISTLE_LENGTH_WEIGHT) / 100.0; m_properties.bristleInkAmountWeight = settings->getDouble(HAIRY_INK_BRISTLE_INK_AMOUNT_WEIGHT) / 100.0; m_properties.inkDepletionWeight = settings->getDouble(HAIRY_INK_DEPLETION_WEIGHT); m_properties.useSoakInk = settings->getBool(HAIRY_INK_SOAK); m_properties.useMousePressure = settings->getBool(HAIRY_BRISTLE_USE_MOUSEPRESSURE); m_properties.shearFactor = settings->getDouble(HAIRY_BRISTLE_SHEAR); m_properties.randomFactor = settings->getDouble(HAIRY_BRISTLE_RANDOM); m_properties.scaleFactor = settings->getDouble(HAIRY_BRISTLE_SCALE); m_properties.threshold = settings->getBool(HAIRY_BRISTLE_THRESHOLD); m_properties.antialias = settings->getBool(HAIRY_BRISTLE_ANTI_ALIASING); m_properties.useCompositing = settings->getBool(HAIRY_BRISTLE_USE_COMPOSITING); m_properties.connectedPath = settings->getBool(HAIRY_BRISTLE_CONNECTED); } KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisHairyPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(0.5); } void KisHairyPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } /** * Even though we don't use spacing in hairy brush, we should still * initialize its distance information to ensure drawing angle and * other history-based sensors work fine. */ KisPaintInformation pi(pi2); KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(currentDistance); // Hairy Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(pi); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(pi); quint8 origOpacity = m_opacityOption.apply(painter(), pi); // we don't use spacing here (the brush itself is used only once // during initialization), so we should just skip the distance info // update - m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_properties.scaleFactor, rotation); + m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_properties.scaleFactor, m_mirrorFlip? -rotation : rotation); //QRect rc = m_dab->exactBounds(); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); // we don't use spacing in hairy brush, but history is // still important for us currentDistance->registerPaintedDab(pi, KisSpacingInformation(), KisTimingInformation()); } diff --git a/plugins/paintops/hairy/kis_hairy_paintop.h b/plugins/paintops/hairy/kis_hairy_paintop.h index e9de44d51d..9bda32cbe2 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.h +++ b/plugins/paintops/hairy/kis_hairy_paintop.h @@ -1,63 +1,65 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_HAIRYPAINTOP_H_ #define KIS_HAIRYPAINTOP_H_ #include #include #include #include #include "hairy_brush.h" #include #include #include class KisPainter; class KisBrushBasedPaintOpSettings; class KisHairyPaintOp : public KisPaintOp { public: KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: KisHairyProperties m_properties; KisPaintDeviceSP m_dab; KisPaintDeviceSP m_dev; HairyBrush m_brush; KisPressureRotationOption m_rotationOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; + bool m_mirrorFlip; + void loadSettings(const KisBrushBasedPaintOpSettings* settings); }; #endif // KIS_HAIRYPAINTOP_H_ diff --git a/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp b/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp index 7213d063f2..42c47291a8 100644 --- a/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp +++ b/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp @@ -1,104 +1,104 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_embedded_pattern_manager.h" #include #include #include #include #include struct KisEmbeddedPatternManager::Private { static KoPattern* tryLoadEmbeddedPattern(const KisPropertiesConfigurationSP setting) { KoPattern *pattern = 0; QByteArray ba = QByteArray::fromBase64(setting->getString("Texture/Pattern/Pattern").toLatin1()); QImage img; img.loadFromData(ba, "PNG"); QString name = setting->getString("Texture/Pattern/Name"); QString filename = setting->getString("Texture/Pattern/PatternFileName"); if (name.isEmpty() || name != QFileInfo(name).fileName()) { QFileInfo info(filename); - name = info.baseName(); + name = info.completeBaseName(); } if (!img.isNull()) { pattern = new KoPattern(img, name, KoResourceServerProvider::instance()->patternServer()->saveLocation()); } return pattern; } }; void KisEmbeddedPatternManager::saveEmbeddedPattern(KisPropertiesConfigurationSP setting, const KoPattern *pattern) { QByteArray patternMD5 = pattern->md5(); /** * The process of saving a pattern may be quite expensive, so * we won't rewrite the pattern if has the same md5-sum and at * least some data is present */ QByteArray existingMD5 = QByteArray::fromBase64(setting->getString("Texture/Pattern/PatternMD5").toLatin1()); QString existingPatternBase64 = setting->getString("Texture/Pattern/PatternMD5").toLatin1(); if (patternMD5 == existingMD5 && !existingPatternBase64.isEmpty()) { return; } setting->setProperty("Texture/Pattern/PatternMD5", patternMD5.toBase64()); setting->setProperty("Texture/Pattern/PatternFileName", pattern->filename()); setting->setProperty("Texture/Pattern/Name", pattern->name()); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); pattern->pattern().save(&buffer, "PNG"); setting->setProperty("Texture/Pattern/Pattern", ba.toBase64()); } KoPattern* KisEmbeddedPatternManager::loadEmbeddedPattern(const KisPropertiesConfigurationSP setting) { KoPattern *pattern = 0; KoResourceServer *server = KoResourceServerProvider::instance()->patternServer(); QByteArray md5 = QByteArray::fromBase64(setting->getString("Texture/Pattern/PatternMD5").toLatin1()); pattern = server->resourceByMD5(md5); if (pattern) return pattern; QString name = setting->getString("Texture/Pattern/Name"); pattern = server->resourceByName(name); if (pattern) return pattern; QString fileName = setting->getString("Texture/Pattern/PatternFileName"); pattern = server->resourceByFilename(fileName); if (pattern) return pattern; pattern = Private::tryLoadEmbeddedPattern(setting); if (pattern) { KoResourceServerProvider::instance()->patternServer()->addResource(pattern, false); } return pattern; } diff --git a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp b/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp index d078b587b5..2d06a50608 100644 --- a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp +++ b/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp @@ -1,213 +1,213 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_embedded_pattern_manager_test.h" #include #include #include #include #include "kis_embedded_pattern_manager.h" #include #include #include "sdk/tests/kistest.h" KoPattern *KisEmbeddedPatternManagerTest::createPattern() { QImage image(512, 512, QImage::Format_ARGB32); image.fill(255); QPainter gc(&image); gc.fillRect(100, 100, 312, 312, Qt::red); KoPattern *pattern = new KoPattern(image, "__test_pattern", KoResourceServerProvider::instance()->patternServer()->saveLocation()); return pattern; } void KisEmbeddedPatternManagerTest::testRoundTrip() { KoPattern *pattern = createPattern(); KisPropertiesConfigurationSP config(new KisPropertiesConfiguration); KisEmbeddedPatternManager::saveEmbeddedPattern(config, pattern); KoPattern *newPattern = KisEmbeddedPatternManager::loadEmbeddedPattern(config); QCOMPARE(newPattern->pattern(), pattern->pattern()); QCOMPARE(newPattern->name(), pattern->name()); QCOMPARE(QFileInfo(newPattern->filename()).fileName(), QFileInfo(pattern->filename()).fileName()); delete pattern; // will be deleted by the server // delete newPattern; } void KisEmbeddedPatternManagerTest::init() { Q_FOREACH(KoPattern *pa, KoResourceServerProvider::instance()->patternServer()->resources()) { if (pa) { KoResourceServerProvider::instance()->patternServer()->removeResourceFile(pa->filename()); } } } KisPropertiesConfigurationSP KisEmbeddedPatternManagerTest::createXML(NameStatus nameStatus, bool hasMd5) { KisPropertiesConfigurationSP setting(new KisPropertiesConfiguration); switch (nameStatus) { case VALID: { setting->setProperty("Texture/Pattern/PatternFileName", "./__test_pattern_path.pat"); setting->setProperty("Texture/Pattern/Name", "__test_pattern"); break; } case PATH: { QString path = KoResourceServerProvider::instance()->patternServer()->saveLocation() + "/__test_pattern.pat"; setting->setProperty("Texture/Pattern/PatternFileName", path); setting->setProperty("Texture/Pattern/Name", "__test_pattern"); break; } case EMPTY: { setting->setProperty("Texture/Pattern/PatternFileName", "./__test_pattern_path.pat"); setting->setProperty("Texture/Pattern/Name", ""); break; } } { KoPattern *pattern = createPattern(); if (hasMd5) { QByteArray patternMD5 = pattern->md5(); Q_ASSERT(!patternMD5.isEmpty()); setting->setProperty("Texture/Pattern/PatternMD5", patternMD5.toBase64()); } QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); pattern->pattern().save(&buffer, "PNG"); setting->setProperty("Texture/Pattern/Pattern", ba.toBase64()); delete pattern; } return setting; } KoPattern* findOnServer(QByteArray md5) { KoPattern *pattern = 0; if (!md5.isEmpty()) { return KoResourceServerProvider::instance()->patternServer()->resourceByMD5(md5); } return pattern; } void KisEmbeddedPatternManagerTest::checkOneConfig(NameStatus nameStatus, bool hasMd5, QString expectedName, bool isOnServer) { QScopedPointer basePattern(createPattern()); KoPattern *initialPattern = findOnServer(basePattern->md5()); QCOMPARE((bool)initialPattern, isOnServer); KisPropertiesConfigurationSP setting = createXML(nameStatus, hasMd5); KoPattern *pattern = KisEmbeddedPatternManager::loadEmbeddedPattern(setting); QVERIFY(pattern); QCOMPARE(pattern->pattern(), basePattern->pattern()); QVERIFY(pattern->name().startsWith(expectedName)); QFileInfo info(pattern->filename()); - QVERIFY(info.baseName().startsWith(expectedName)); + QVERIFY(info.completeBaseName().startsWith(expectedName)); QCOMPARE(info.dir().path(), QDir(KoResourceServerProvider::instance()->patternServer()->saveLocation()).path()); // We can only find things on the server by name or by md5; the file path as an identifier does not work. if (isOnServer && nameStatus != EMPTY && !hasMd5) { QCOMPARE(initialPattern, pattern); } // will be deleted by the server // delete pattern; } void KisEmbeddedPatternManagerTest::testLoadingNotOnServerValidName() { checkOneConfig(VALID, false, "__test_pattern", false); } void KisEmbeddedPatternManagerTest::testLoadingNotOnServerEmptyName() { checkOneConfig(EMPTY, false, "__test_pattern_path", false); } void KisEmbeddedPatternManagerTest::testLoadingNotOnServerPathName() { checkOneConfig(PATH, false, "__test_pattern", false); } void KisEmbeddedPatternManagerTest::testLoadingOnServerValidName() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(VALID, false, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerEmptyName() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(EMPTY, false, "__test_pattern_path", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerPathName() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(PATH, false, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerValidNameMd5() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), true); checkOneConfig(VALID, true, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerEmptyNameMd5() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), true); checkOneConfig(EMPTY, true, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerPathNameMd5() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(PATH, true, "__test_pattern", true); } KISTEST_MAIN(KisEmbeddedPatternManagerTest) diff --git a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index b284692bfa..835e341b20 100644 --- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,57 +1,57 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=assignprofiledialog X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Assign Profile to Image Name[ar]=إسناد اللاحات إلى الصور Name[ca]=Assigna un perfil a una imatge Name[ca@valencia]=Assigna un perfil a una imatge Name[cs]=Přiřadit obrázku profil Name[el]=Αντιστοίχιση προφίλ σε εικόνα Name[en_GB]=Assign Profile to Image Name[es]=Asignar perfil a imagen Name[eu]=Esleitu profila irudiari Name[fi]=Liitä kuvaan profiili Name[fr]=Attribuer un profil à l'image Name[gl]=Asignar un perfil á imaxe Name[is]=Úthluta litasniði á myndina Name[it]=Assegna profilo a immagine Name[ko]=이미지에 프로필 할당 Name[nl]=Profiel aan afbeelding toewijzen Name[nn]=Tildel profil til bilete Name[pl]=Przypisz profil do obrazu Name[pt]=Atribuir um Perfil à Imagem Name[pt_BR]=Atribuir perfil a imagem Name[sv]=Tilldela profil till bild Name[tr]=Görüntüye Profil Ata Name[uk]=Призначити профіль до зображення Name[x-test]=xxAssign Profile to Imagexx Name[zh_CN]=为图像指定特性文件 Name[zh_TW]=指定設定檔到圖像 Comment=Assign a profile to an image without converting it. Comment[ar]=أسنِد لاحة إلى صورة دون تحويلها. Comment[ca]=Assigna un perfil a una imatge sense convertir-la. Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la. Comment[el]=Αντιστοιχίζει ένα προφίλ σε μια εικόνα χωρίς μετατροπή. Comment[en_GB]=Assign a profile to an image without converting it. Comment[es]=Asignar un perfil a una imagen sin convertirla. Comment[eu]=Esleitu profil bat irudi bati hura bihurtu gabe. Comment[fi]=Liitä kuvaan profiili muuntamatta kuvaa Comment[fr]=Attribuer un profil à une image sans la convertir. Comment[gl]=Asignar un perfil a unha imaxe sen convertela. Comment[is]=Úthluta litasniði á myndina án þess að umbreyta henni. Comment[it]=Assegna un profilo a un'immagine senza convertirla. Comment[ko]=프로필을 변환하지 않고 이미지에 할당합니다. Comment[nl]=Een profiel aan een afbeelding toewijzen zonder het te converteren. -Comment[nn]=Tildel profil til eit bilete utan å konvertera det. +Comment[nn]=Tildel profil til eit bilete utan å konvertera det Comment[pl]=Przypisz profil do obrazu bez jego przekształcania. Comment[pt]=Atribui um perfil à imagem sem a converter. Comment[pt_BR]=Atribui um perfil para uma imagem sem convertê-la. Comment[sv]=Tilldela en profil till en bild utan att konvertera den. Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata. Comment[uk]=Призначити профіль до зображення без його перетворення. Comment[x-test]=xxAssign a profile to an image without converting it.xx Comment[zh_CN]=仅为图像指定特性文件,不转换其色彩空间 Comment[zh_TW]=將設定檔指定給圖像,而不進行轉換。 diff --git a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop index e4fe9fb501..df0266eec2 100644 --- a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop +++ b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop @@ -1,56 +1,58 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=comics_project_management_tools X-Krita-Manual=README.html X-Python-2-Compatible=false Name=Comics Project Management Tools Name[ar]=أدوات إدارة المشاريع الهزليّة Name[ca]=Eines per a la gestió dels projectes de còmics Name[ca@valencia]=Eines per a la gestió dels projectes de còmics Name[cs]=Nástroje pro správu projektů komixů Name[el]=Εργαλεία διαχείρισης έργων ιστοριών σε εικόνες Name[en_GB]=Comics Project Management Tools Name[es]=Herramientas de gestión de proyectos de cómics Name[eu]=Komikien proiektuak kudeatzeko tresnak Name[fi]=Sarjakuvaprojektien hallintatyökalut Name[fr]=Outils de gestion d'un projet de bande dessinée Name[gl]=Ferramentas de xestión de proxectos de cómics Name[is]=Verkefnisstjórn teiknimyndasögu Name[it]=Strumenti per la gestione dei progetti di fumetti Name[ko]=만화 프로젝트 관리 도구 Name[nl]=Hulpmiddelen voor projectbeheer van strips Name[nn]=Prosjekthandsamingsverktøy for teikneseriar Name[pl]=Narzędzia do zarządzania projektami komiksów Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada +Name[pt_BR]=Ferramentas de gerenciamento de projeto de quadrinhos Name[sv]=Projekthanteringsverktyg för tecknade serier Name[tr]=Çizgi Roman Projesi Yönetimi Araçları Name[uk]=Інструменти для керування проєктами коміксів Name[x-test]=xxComics Project Management Toolsxx Name[zh_CN]=漫画项目管理工具 Name[zh_TW]=漫畫專案管理工具 Comment=Tools for managing comics. Comment[ar]=أدوات لإدارة الهزليّات. Comment[ca]=Eines per a gestionar els còmics. Comment[ca@valencia]=Eines per a gestionar els còmics. Comment[cs]=Nástroje pro správu komixů. Comment[el]=Εργαλεία για τη διαχείριση ιστοριών σε εικόνες. Comment[en_GB]=Tools for managing comics. Comment[es]=Herramientas para gestionar cómics. Comment[eu]=Komikiak kudeatzeko tresnak. Comment[fi]=Sarjakuvien hallintatyökalut. Comment[fr]=Outils pour gérer les bandes dessinées. Comment[gl]=Ferramentas para xestionar cómics. Comment[is]=Verkfæri til að stýra gerð teiknimyndasögu. Comment[it]=Strumenti per la gestione dei fumetti. Comment[ko]=만화 관리 도구입니다. Comment[nl]=Hulpmiddelen voor beheer van strips. -Comment[nn]=Verktøy for handsaming av teikneseriar. +Comment[nn]=Verktøy for handsaming av teikneseriar Comment[pl]=Narzędzie do zarządzania komiksami. Comment[pt]=Ferramentas para gerir bandas desenhadas. +Comment[pt_BR]=Ferramentas para gerenciar quadrinhos. Comment[sv]=Verktyg för att hantera tecknade serier. Comment[tr]=Çizgi romanları yönetmek için araçlar. Comment[uk]=Інструменти для керування коміксами Comment[x-test]=xxTools for managing comics.xx Comment[zh_CN]=用于管理漫画的工具。 Comment[zh_TW]=管理漫畫的工具。 diff --git a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop index 90b6455b77..3d70df7b0c 100644 --- a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop +++ b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop @@ -1,45 +1,45 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=krita_script_starter X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Krita Script Starter Name[ca]=Iniciador de scripts del Krita Name[ca@valencia]=Iniciador de scripts del Krita Name[cs]=Spouštěč skriptů Krita Name[en_GB]=Krita Script Starter Name[es]=Iniciador de guiones de Krita Name[eu]=Krita-ren script abiarazlea Name[gl]=Iniciador de scripts de Krita Name[it]=Iniziatore di script per Krita Name[ko]=Krita 스크립트 시작 도구 Name[nl]=Script-starter van Krita Name[nn]=Krita skriptbyggjar Name[pl]=Starter skryptów Krity Name[pt]=Inicialização do Programa do Krita Name[sv]=Krita skriptstart Name[tr]=Krita Betik Başlatıcı Name[uk]=Створення скрипту Krita Name[x-test]=xxKrita Script Starterxx Name[zh_CN]=Krita 空脚本生成器 Name[zh_TW]=Krita 指令啟動器 Comment=Create the metadata and file structure for a new Krita script Comment[ca]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[ca@valencia]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[en_GB]=Create the metadata and file structure for a new Krita script Comment[es]=Crear los metadatos y la estructura de archivos para un nuevo guion de Krita Comment[eu]=Sortu Krita-script berri baterako meta-datuak eta fitxategi egitura Comment[gl]=Crear os metadatos e a estrutura de ficheiros para un novo script de Krita. Comment[it]=Crea i metadati e la struttura dei file per un nuovo script di Krita Comment[ko]=새 Krita 스크립트에 대한 메타데이터 및 파일 구조 생성 Comment[nl]=Maak de metagegevens en bestandsstructuur voor een nieuw Krita-script -Comment[nn]=Lag metadata og filstruktur for nytt Krita-skript +Comment[nn]=Generer metadata og filstruktur for nytt Krita-skript Comment[pl]=Utwórz metadane i strukturę plików dla nowego skryptu Krity Comment[pt]=Cria os meta-dados e a estrutura de ficheiros para um novo programa do Krita Comment[sv]=Skapa metadata och filstruktur för ett nytt Krita-skript Comment[tr]=Yeni Krita betiği için üstveri ve dosya yapısı oluştur Comment[uk]=Створення метаданих і структури файлів для нового скрипту Krita Comment[x-test]=xxCreate the metadata and file structure for a new Krita scriptxx Comment[zh_CN]=为新的 Krita 脚本创建元数据及文件结构 Comment[zh_TW]=為新的 Krita 指令稿建立中繼資料和檔案建構體 diff --git a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop index 1e1ccfb596..2603892208 100644 --- a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop +++ b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop @@ -1,55 +1,55 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=palette_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Palette docker Name[ar]=رصيف اللوحات Name[ca]=Acoblador Paleta Name[ca@valencia]=Acoblador Paleta Name[cs]=Dok palet Name[de]=Paletten-Docker Name[el]=Προσάρτηση παλέτας Name[en_GB]=Palette docker Name[es]=Panel de paleta Name[eu]=Paleta-panela Name[fi]=Palettitelakka Name[fr]=Panneau de palette Name[gl]=Doca de paleta Name[is]=Tengikví fyrir litaspjald Name[it]=Area di aggancio della tavolozza Name[ko]=팔레트 도킹 패널 Name[nl]=Vastzetter van palet Name[nn]=Palettdokk Name[pl]=Dok palety Name[pt]=Área acoplável da paleta Name[sv]=Dockningsfönster för palett Name[tr]=Palet doku Name[uk]=Панель палітри Name[x-test]=xxPalette dockerxx Name[zh_CN]=调色板工具面板 Name[zh_TW]=「調色盤」面板 Comment=A Python-based docker to edit color palettes. Comment[ar]=رصيف بِ‍«پيثون» لتحرير لوحات الألوان. Comment[ca]=Un acoblador basat en Python per editar paletes de colors. Comment[ca@valencia]=Un acoblador basat en Python per editar paletes de colors. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος. Comment[en_GB]=A Python-based docker to edit colour palettes. Comment[es]=Un panel basado en Python para editar paletas de colores. Comment[eu]=Kolore-paletak editatzeko Python-oinarridun paleta bat. Comment[fi]=Python-pohjainen telakka väripalettien muokkaamiseen. Comment[fr]=Panneau en Python pour éditer les palettes de couleurs. Comment[gl]=Unha doca baseada en Python para editar paletas de cores. Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python. Comment[ko]=색상 팔레트를 편집할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken. -Comment[nn]=Python-basert dokk for redigering av fargepalettar. +Comment[nn]=Python-basert dokk for redigering av fargepalettar Comment[pl]=Dok oparty na pythonie do edytowania palet barw. Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores. Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter. Comment[tr]=Renk paletlerini düzenlemek için Python-tabanlı bir dok. Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python. Comment[x-test]=xxA Python-based docker to edit color palettes.xx Comment[zh_CN]=基于 Python 的调色板编辑器工具面板 Comment[zh_TW]=基於 Python 的面板,用於編輯調色盤。 diff --git a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop index af0548a30b..6dc82d0dda 100644 --- a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop +++ b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop @@ -1,44 +1,44 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=plugin_importer X-Python-2-Compatible=false X-Krita-Manual=manual.html Name=Python Plugin Importer Name[ca]=Importador de connectors en Python Name[ca@valencia]=Importador de connectors en Python Name[cs]=Importér modulů pro Python Name[en_GB]=Python Plugin Importer Name[es]=Importador de complementos de Python Name[gl]=Importador de complementos de Python Name[it]=Importatore estensioni Python Name[ko]=Python 플러그인 가져오기 도구 Name[nl]=Importeur van Plugin voor Python Name[nn]=Importering av Python-tillegg Name[pl]=Import wtyczek Pythona Name[pt]=Importador de 'Plugins' do Python Name[sv]=Python-insticksimport Name[tr]=Python Eklenti İçe Aktarıcı Name[uk]=Засіб імпортування додатків Python Name[x-test]=xxPython Plugin Importerxx Name[zh_CN]=Python 插件导入器 Name[zh_TW]=Python 外掛程式匯入工具 Comment=Imports Python plugins from zip files. Comment[ca]=Importa connectors en Python a partir de fitxers «zip». Comment[ca@valencia]=Importa connectors en Python a partir de fitxers «zip». Comment[cs]=Importuje moduly Pythonu ze souborů zip. Comment[en_GB]=Imports Python plugins from zip files. Comment[es]=Importa complementos de Python desde archivos zip. Comment[gl]=Importa complementos de Python de ficheiros zip. Comment[it]=Importa le estensioni Python dai file compressi. Comment[ko]=ZIP 파일에서 Python 플러그인을 가져옵니다. Comment[nl]=Importeert Python-plug-ins uit zip-bestanden. -Comment[nn]=Importerer Python-baserte programtillegg frå .zip-filer. +Comment[nn]=Importerer Python-baserte programtillegg frå .zip-filer Comment[pl]=Importuj wtyczki Pythona z plików zip. Comment[pt]=Importa os 'plugins' em Python a partir de ficheiros Zip. Comment[sv]=Importerar Python-insticksprogram från zip-filer. Comment[tr]=Python eklentilerini zip dosyasından içe aktarır. Comment[uk]=Імпортує додатки Python з файлів zip. Comment[x-test]=xxImports Python plugins from zip files.xx Comment[zh_CN]=从 zip 文件导入 Python 插件。 Comment[zh_TW]=匯入 Zip 檔案中的 Python 外掛程式。 diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index dd4d9681a0..9d5ec5e925 100644 --- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,53 +1,53 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=quick_settings_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Quick Settings Docker Name[ar]=رصيف بإعدادات سريعة Name[ca]=Acoblador Arranjament ràpid Name[ca@valencia]=Acoblador Arranjament ràpid Name[cs]=Dok pro rychlé nastavení Name[el]=Προσάρτηση γρήγορων ρυθμίσεων Name[en_GB]=Quick Settings Docker Name[es]=Panel de ajustes rápidos Name[eu]=Ezarpen azkarren panela Name[fi]=Pika-asetustelakka Name[fr]=Réglages rapides Name[gl]=Doca de configuración rápida Name[it]=Area di aggancio delle impostazioni rapide Name[ko]=빠른 설정 도킹 패널 Name[nl]=Verankering voor snelle instellingen Name[nn]=Snøgginnstillingar-dokk Name[pl]=Dok szybkich ustawień Name[pt]=Área de Configuração Rápida Name[sv]=Dockningspanel med snabbinställningar Name[tr]=Hızlı Ayarlar Doku Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Name[zh_CN]=快速设置工具面板 Name[zh_TW]=「快速設定」面板 Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة. Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου. Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat. Comment[fi]=Python-pohjainen telakka siveltimen koon ja läpikuultavuuden nopean muuttamiseen. Comment[fr]=Panneau en python pour modifier rapidement la taille et l'opacité des brosses. Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis. Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità. Comment[ko]=브러시 크기와 불투명도를 빠르게 변경할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. -Comment[nn]=Python-basert dokk for kjapt endring av penselstorleik/-tettleik. +Comment[nn]=Python-basert dokk for kjapp endring av penselstorleik/-tettleik Comment[pl]=Dok oparty na Pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. Comment[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok. Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx Comment[zh_CN]=基于 Python 的用于快速更改笔刷尺寸和透明度的工具面板。 Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。 diff --git a/plugins/python/scripter/icons/reload_script.svg b/plugins/python/scripter/icons/reload_script.svg new file mode 100644 index 0000000000..86269d0348 --- /dev/null +++ b/plugins/python/scripter/icons/reload_script.svg @@ -0,0 +1,79 @@ + +image/svg+xml \ No newline at end of file diff --git a/plugins/python/scripter/resources_rc.py b/plugins/python/scripter/resources_rc.py index 09734cdc5d..3b603b1ade 100644 --- a/plugins/python/scripter/resources_rc.py +++ b/plugins/python/scripter/resources_rc.py @@ -1,544 +1,847 @@ # -*- coding: utf-8 -*- # Resource object code # -# Created by: The Resource Compiler for PyQt5 (Qt v5.8.0) +# Created by: The Resource Compiler for PyQt5 (Qt v5.12.2) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ +\x00\x00\x03\x58\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\ +\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\ +\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\x30\x2e\x30\x2c\ +\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\ +\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\ +\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\ +\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\ +\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ +\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ +\x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x34\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ +\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x65\x6e\x61\x62\x6c\ +\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\x22\x6e\x65\ +\x77\x20\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x78\x6d\x6c\ +\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\ +\x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x39\x2e\ +\x34\x38\x38\x2c\x33\x2e\x39\x30\x37\x48\x32\x34\x76\x32\x2e\x37\ +\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x33\x2e\x39\x30\x37\x7a\x20\ +\x4d\x31\x31\x2e\x31\x36\x33\x2c\x38\x2e\x33\x37\x32\x48\x32\x34\ +\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\x31\x36\x33\x56\x38\x2e\ +\x33\x37\x32\x7a\x20\x4d\x31\x31\x2e\x31\x36\x33\x2c\x31\x32\x2e\ +\x38\x33\x37\x48\x32\x34\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\ +\x31\x36\x33\x56\x31\x32\x2e\x38\x33\x37\x7a\x0d\x0a\x09\x20\x4d\ +\x39\x2e\x34\x38\x38\x2c\x31\x37\x2e\x33\x30\x32\x48\x32\x34\x76\ +\x32\x2e\x37\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x31\x37\x2e\x33\ +\x30\x32\x7a\x20\x4d\x32\x2e\x37\x39\x31\x2c\x31\x30\x2e\x36\x30\ +\x34\x63\x30\x2c\x31\x2e\x32\x33\x33\x2c\x31\x2c\x32\x2e\x32\x33\ +\x33\x2c\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\x33\x33\x48\x36\x2e\ +\x31\x34\x76\x2d\x32\x2e\x32\x33\x33\x6c\x33\x2e\x37\x36\x37\x2c\ +\x33\x2e\x36\x32\x38\x4c\x36\x2e\x31\x34\x2c\x31\x37\x2e\x38\x36\ +\x76\x2d\x32\x2e\x32\x33\x32\x48\x35\x2e\x30\x32\x33\x0d\x0a\x09\ +\x43\x32\x2e\x32\x34\x39\x2c\x31\x35\x2e\x36\x32\x38\x2c\x30\x2c\ +\x31\x33\x2e\x33\x37\x39\x2c\x30\x2c\x31\x30\x2e\x36\x30\x34\x56\ +\x38\x2e\x39\x33\x63\x30\x2d\x32\x2e\x37\x37\x34\x2c\x32\x2e\x32\ +\x34\x39\x2d\x35\x2e\x30\x32\x33\x2c\x35\x2e\x30\x32\x33\x2d\x35\ +\x2e\x30\x32\x33\x48\x36\x2e\x31\x34\x76\x32\x2e\x37\x39\x31\x48\ +\x35\x2e\x30\x32\x33\x63\x2d\x31\x2e\x32\x33\x33\x2c\x30\x2d\x32\ +\x2e\x32\x33\x33\x2c\x31\x2d\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\ +\x33\x32\x56\x31\x30\x2e\x36\x30\x34\x7a\x22\x2f\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ \x00\x00\x0f\xd9\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ \x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ \x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ \x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ \x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ \x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ \x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\x3e\x0a\x09\x3c\ \x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x37\x36\x2e\x34\x39\x33\ \x2c\x33\x31\x30\x2e\x39\x39\x38\x63\x2d\x31\x2e\x35\x37\x37\x2d\ \x32\x39\x2e\x31\x36\x37\x2d\x32\x34\x2e\x39\x37\x32\x2d\x35\x32\ \x2e\x35\x36\x32\x2d\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x33\x39\x76\x2d\x32\x31\x2e\x37\x32\x68\x2d\x36\x2e\x32\x33\x35\ \x76\x32\x31\x2e\x37\x32\x20\x20\x20\x63\x2d\x32\x39\x2e\x31\x36\ \x37\x2c\x31\x2e\x35\x37\x37\x2d\x35\x32\x2e\x35\x36\x32\x2c\x32\ \x34\x2e\x39\x37\x32\x2d\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\ \x31\x33\x39\x68\x2d\x32\x31\x2e\x34\x36\x35\x76\x36\x2e\x32\x33\ \x35\x68\x32\x31\x2e\x34\x36\x35\x63\x31\x2e\x35\x37\x37\x2c\x32\ \x39\x2e\x31\x36\x37\x2c\x32\x34\x2e\x39\x37\x32\x2c\x35\x32\x2e\ \x35\x36\x38\x2c\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\x31\x34\ \x35\x76\x32\x31\x2e\x37\x31\x35\x68\x36\x2e\x32\x33\x35\x76\x2d\ \x32\x31\x2e\x37\x31\x35\x20\x20\x20\x63\x32\x39\x2e\x31\x36\x37\ \x2d\x31\x2e\x35\x37\x36\x2c\x35\x32\x2e\x35\x36\x32\x2d\x32\x34\ \x2e\x39\x37\x38\x2c\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x34\x35\x68\x32\x36\x2e\x31\x34\x31\x76\x2d\x36\x2e\x32\x33\x35\ \x48\x33\x37\x36\x2e\x34\x39\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\ \x31\x39\x2c\x33\x36\x35\x2e\x31\x34\x33\x63\x2d\x32\x35\x2e\x37\ \x33\x33\x2d\x31\x2e\x35\x36\x2d\x34\x36\x2e\x33\x34\x35\x2d\x32\ \x32\x2e\x31\x37\x38\x2d\x34\x37\x2e\x39\x30\x33\x2d\x34\x37\x2e\ \x39\x31\x20\x20\x20\x68\x34\x37\x2e\x39\x30\x33\x56\x33\x36\x35\ \x2e\x31\x34\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\x31\x39\x2c\x33\ \x31\x30\x2e\x39\x39\x38\x68\x2d\x34\x37\x2e\x39\x30\x33\x63\x31\ \x2e\x35\x35\x39\x2d\x32\x35\x2e\x37\x33\x32\x2c\x32\x32\x2e\x31\ \x37\x2d\x34\x36\x2e\x33\x34\x35\x2c\x34\x37\x2e\x39\x30\x33\x2d\ \x34\x37\x2e\x39\x30\x33\x56\x33\x31\x30\x2e\x39\x39\x38\x7a\x20\ \x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\x33\ \x76\x2d\x34\x37\x2e\x39\x31\x68\x34\x37\x2e\x39\x30\x33\x20\x20\ \x20\x43\x33\x36\x38\x2e\x36\x39\x32\x2c\x33\x34\x32\x2e\x39\x36\ \x35\x2c\x33\x34\x38\x2e\x30\x38\x37\x2c\x33\x36\x33\x2e\x35\x38\ \x33\x2c\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\ \x33\x7a\x20\x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x31\x30\x2e\ \x39\x39\x38\x76\x2d\x34\x37\x2e\x39\x30\x33\x63\x32\x35\x2e\x37\ \x33\x32\x2c\x31\x2e\x35\x35\x39\x2c\x34\x36\x2e\x33\x34\x34\x2c\ \x32\x32\x2e\x31\x37\x31\x2c\x34\x37\x2e\x39\x30\x33\x2c\x34\x37\ \x2e\x39\x30\x33\x48\x33\x32\x32\x2e\x33\x35\x34\x7a\x20\x20\x20\ \x20\x4d\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\x31\x36\ \x63\x2d\x30\x2e\x37\x35\x35\x2d\x34\x2e\x36\x37\x36\x2c\x32\x2e\ \x34\x33\x2d\x39\x2e\x30\x39\x31\x2c\x37\x2e\x31\x30\x36\x2d\x39\ \x2e\x38\x35\x32\x63\x32\x2e\x30\x36\x34\x2d\x30\x2e\x33\x33\x38\ \x2c\x35\x30\x2e\x35\x39\x35\x2d\x38\x2e\x37\x31\x36\x2c\x35\x33\ \x2e\x35\x31\x32\x2d\x35\x30\x2e\x35\x33\x31\x20\x20\x20\x63\x30\ \x2e\x33\x32\x32\x2d\x34\x2e\x36\x37\x37\x2c\x34\x2e\x33\x36\x35\ \x2d\x38\x2e\x32\x35\x37\x2c\x39\x2e\x31\x35\x31\x2d\x37\x2e\x39\ \x37\x34\x63\x34\x2e\x37\x33\x37\x2c\x30\x2e\x33\x33\x35\x2c\x38\ \x2e\x33\x31\x32\x2c\x34\x2e\x34\x34\x35\x2c\x37\x2e\x39\x38\x39\ \x2c\x39\x2e\x31\x37\x63\x2d\x33\x2e\x38\x37\x39\x2c\x35\x35\x2e\ \x35\x31\x32\x2d\x36\x37\x2e\x32\x39\x31\x2c\x36\x36\x2e\x32\x30\ \x31\x2d\x36\x37\x2e\x39\x33\x31\x2c\x36\x36\x2e\x33\x30\x32\x20\ \x20\x20\x63\x2d\x30\x2e\x34\x33\x38\x2c\x30\x2e\x30\x36\x37\x2d\ \x30\x2e\x39\x30\x31\x2c\x30\x2e\x31\x30\x34\x2d\x31\x2e\x33\x35\ \x38\x2c\x30\x2e\x31\x30\x34\x43\x32\x39\x35\x2e\x35\x37\x34\x2c\ \x39\x37\x2e\x32\x33\x35\x2c\x32\x39\x32\x2e\x30\x31\x32\x2c\x39\ \x34\x2e\x32\x2c\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\ \x31\x36\x7a\x20\x4d\x33\x32\x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\ \x33\x20\x20\x20\x63\x2d\x30\x2e\x33\x32\x39\x2d\x34\x2e\x37\x32\ \x35\x2c\x33\x2e\x32\x34\x39\x2d\x38\x2e\x38\x33\x35\x2c\x37\x2e\ \x39\x37\x31\x2d\x39\x2e\x31\x36\x34\x63\x34\x2e\x38\x37\x34\x2d\ \x30\x2e\x33\x34\x34\x2c\x38\x2e\x38\x34\x31\x2c\x33\x2e\x32\x39\ \x31\x2c\x39\x2e\x31\x37\x2c\x37\x2e\x39\x36\x38\x63\x32\x2e\x39\ \x32\x2c\x34\x31\x2e\x38\x32\x2c\x35\x31\x2e\x34\x34\x31\x2c\x35\ \x30\x2e\x31\x39\x39\x2c\x35\x33\x2e\x35\x31\x32\x2c\x35\x30\x2e\ \x35\x33\x37\x20\x20\x20\x63\x34\x2e\x36\x37\x2c\x30\x2e\x37\x36\ \x31\x2c\x37\x2e\x38\x35\x38\x2c\x35\x2e\x31\x37\x36\x2c\x37\x2e\ \x31\x30\x39\x2c\x39\x2e\x38\x34\x36\x63\x2d\x30\x2e\x36\x37\x39\ \x2c\x34\x2e\x31\x38\x34\x2d\x34\x2e\x32\x34\x31\x2c\x37\x2e\x32\ \x32\x35\x2d\x38\x2e\x34\x37\x33\x2c\x37\x2e\x32\x32\x35\x63\x2d\ \x30\x2e\x34\x35\x37\x2c\x30\x2d\x30\x2e\x39\x31\x36\x2d\x30\x2e\ \x30\x33\x37\x2d\x31\x2e\x33\x37\x39\x2d\x30\x2e\x31\x31\x20\x20\ \x20\x43\x39\x39\x2e\x36\x36\x38\x2c\x39\x37\x2e\x30\x33\x31\x2c\ \x33\x36\x2e\x32\x35\x35\x2c\x38\x36\x2e\x33\x34\x31\x2c\x33\x32\ \x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\x33\x7a\x20\x4d\x33\x33\x30\ \x2e\x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x63\x32\x2e\x34\ \x34\x38\x2c\x30\x2e\x31\x30\x31\x2c\x36\x30\x2e\x39\x32\x32\x2c\ \x33\x2e\x30\x36\x39\x2c\x37\x32\x2e\x36\x35\x35\x2c\x35\x30\x2e\ \x30\x31\x37\x20\x20\x20\x63\x30\x2e\x35\x35\x35\x2c\x32\x2e\x32\ \x32\x39\x2c\x30\x2e\x32\x31\x34\x2c\x34\x2e\x35\x33\x36\x2d\x30\ \x2e\x39\x36\x32\x2c\x36\x2e\x35\x30\x33\x63\x2d\x31\x2e\x31\x38\ \x31\x2c\x31\x2e\x39\x36\x37\x2d\x33\x2e\x30\x36\x32\x2c\x33\x2e\ \x33\x36\x31\x2d\x35\x2e\x32\x39\x31\x2c\x33\x2e\x39\x31\x63\x2d\ \x30\x2e\x36\x37\x2c\x30\x2e\x31\x38\x32\x2d\x31\x2e\x33\x38\x32\ \x2c\x30\x2e\x32\x36\x38\x2d\x32\x2e\x30\x38\x33\x2c\x30\x2e\x32\ \x36\x38\x20\x20\x20\x63\x2d\x33\x2e\x39\x34\x35\x2c\x30\x2d\x37\ \x2e\x33\x36\x37\x2d\x32\x2e\x36\x38\x2d\x38\x2e\x33\x33\x2d\x36\ \x2e\x35\x30\x34\x63\x2d\x38\x2e\x35\x39\x32\x2d\x33\x34\x2e\x33\ \x38\x32\x2d\x35\x36\x2e\x32\x37\x2d\x33\x37\x2e\x30\x31\x2d\x35\ \x36\x2e\x37\x35\x2d\x33\x37\x2e\x30\x32\x37\x63\x2d\x32\x2e\x32\ \x38\x34\x2d\x30\x2e\x30\x39\x35\x2d\x34\x2e\x34\x30\x33\x2d\x31\ \x2e\x30\x37\x38\x2d\x35\x2e\x39\x35\x36\x2d\x32\x2e\x37\x36\x38\ \x20\x20\x20\x63\x2d\x31\x2e\x35\x35\x33\x2d\x31\x2e\x36\x39\x2d\ \x32\x2e\x33\x35\x36\x2d\x33\x2e\x38\x38\x35\x2d\x32\x2e\x32\x35\ \x39\x2d\x36\x2e\x31\x38\x31\x63\x30\x2e\x31\x39\x35\x2d\x34\x2e\ \x36\x31\x32\x2c\x33\x2e\x39\x33\x39\x2d\x38\x2e\x32\x32\x39\x2c\ \x38\x2e\x35\x33\x31\x2d\x38\x2e\x32\x32\x39\x4c\x33\x33\x30\x2e\ \x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x7a\x20\x4d\x37\x33\ \x2e\x36\x38\x36\x2c\x31\x37\x39\x2e\x36\x39\x38\x20\x20\x20\x63\ \x2d\x30\x2e\x34\x37\x32\x2c\x30\x2e\x30\x32\x34\x2d\x34\x38\x2e\ \x31\x39\x33\x2c\x32\x2e\x37\x36\x31\x2d\x35\x36\x2e\x37\x36\x2c\ \x33\x37\x2e\x30\x32\x37\x63\x2d\x30\x2e\x39\x35\x39\x2c\x33\x2e\ \x38\x32\x34\x2d\x34\x2e\x33\x38\x31\x2c\x36\x2e\x35\x30\x34\x2d\ \x38\x2e\x33\x33\x2c\x36\x2e\x35\x30\x34\x63\x2d\x30\x2e\x37\x30\ \x33\x2c\x30\x2d\x31\x2e\x34\x31\x2d\x30\x2e\x30\x38\x36\x2d\x32\ \x2e\x30\x38\x38\x2d\x30\x2e\x32\x36\x38\x20\x20\x20\x63\x2d\x32\ \x2e\x32\x32\x2d\x30\x2e\x35\x34\x39\x2d\x34\x2e\x30\x39\x35\x2d\ \x31\x2e\x39\x34\x33\x2d\x35\x2e\x32\x38\x32\x2d\x33\x2e\x39\x31\ \x63\x2d\x31\x2e\x31\x37\x39\x2d\x31\x2e\x39\x36\x37\x2d\x31\x2e\ \x35\x32\x2d\x34\x2e\x32\x37\x34\x2d\x30\x2e\x39\x36\x35\x2d\x36\ \x2e\x35\x30\x33\x63\x31\x31\x2e\x37\x33\x34\x2d\x34\x36\x2e\x39\ \x34\x37\x2c\x37\x30\x2e\x32\x30\x38\x2d\x34\x39\x2e\x39\x31\x36\ \x2c\x37\x32\x2e\x36\x39\x32\x2d\x35\x30\x2e\x30\x32\x32\x6c\x30\ \x2e\x33\x35\x39\x2d\x30\x2e\x30\x30\x36\x20\x20\x20\x63\x34\x2e\ \x36\x31\x35\x2c\x30\x2c\x38\x2e\x33\x38\x31\x2c\x33\x2e\x36\x31\ \x31\x2c\x38\x2e\x35\x38\x2c\x38\x2e\x32\x31\x37\x43\x38\x32\x2e\ \x30\x39\x38\x2c\x31\x37\x35\x2e\x34\x37\x31\x2c\x37\x38\x2e\x34\ \x32\x2c\x31\x37\x39\x2e\x34\x38\x37\x2c\x37\x33\x2e\x36\x38\x36\ \x2c\x31\x37\x39\x2e\x36\x39\x38\x7a\x20\x4d\x31\x36\x30\x2e\x39\ \x32\x35\x2c\x34\x39\x2e\x31\x30\x36\x20\x20\x20\x63\x2d\x30\x2e\ \x32\x31\x39\x2d\x31\x2e\x37\x30\x38\x2d\x30\x2e\x33\x33\x32\x2d\ \x33\x2e\x34\x32\x38\x2d\x30\x2e\x33\x33\x32\x2d\x35\x2e\x31\x33\ \x33\x63\x30\x2d\x32\x32\x2e\x32\x38\x39\x2c\x31\x38\x2e\x31\x34\ \x2d\x34\x30\x2e\x34\x32\x39\x2c\x34\x30\x2e\x34\x32\x39\x2d\x34\ \x30\x2e\x34\x32\x39\x63\x32\x32\x2e\x32\x39\x33\x2c\x30\x2c\x34\ \x30\x2e\x34\x32\x36\x2c\x31\x38\x2e\x31\x34\x2c\x34\x30\x2e\x34\ \x32\x36\x2c\x34\x30\x2e\x34\x32\x39\x20\x20\x20\x63\x30\x2c\x31\ \x2e\x37\x30\x35\x2d\x30\x2e\x31\x31\x35\x2c\x33\x2e\x34\x31\x39\ \x2d\x30\x2e\x33\x33\x35\x2c\x35\x2e\x31\x33\x33\x63\x32\x34\x2e\ \x32\x36\x36\x2c\x31\x30\x2e\x36\x39\x32\x2c\x34\x35\x2e\x32\x33\ \x36\x2c\x33\x31\x2e\x31\x31\x33\x2c\x35\x39\x2e\x32\x39\x31\x2c\ \x35\x37\x2e\x37\x37\x31\x6c\x31\x2e\x36\x30\x37\x2c\x33\x2e\x30\ \x35\x34\x6c\x2d\x33\x2e\x30\x36\x39\x2c\x31\x2e\x35\x38\x39\x20\ \x20\x20\x63\x2d\x32\x36\x2e\x33\x34\x38\x2c\x31\x33\x2e\x37\x30\ \x37\x2d\x35\x35\x2e\x37\x35\x32\x2c\x32\x30\x2e\x36\x35\x38\x2d\ \x38\x37\x2e\x33\x39\x32\x2c\x32\x30\x2e\x36\x35\x38\x6c\x30\x2c\ \x30\x63\x2d\x34\x36\x2e\x38\x34\x31\x2c\x30\x2d\x38\x36\x2e\x38\ \x30\x34\x2d\x31\x35\x2e\x30\x31\x33\x2d\x31\x30\x36\x2e\x34\x35\ \x37\x2d\x32\x33\x2e\x39\x36\x37\x6c\x2d\x33\x2e\x33\x39\x38\x2d\ \x31\x2e\x35\x35\x33\x6c\x31\x2e\x38\x32\x37\x2d\x33\x2e\x32\x36\ \x31\x20\x20\x20\x43\x31\x31\x37\x2e\x34\x39\x34\x2c\x37\x38\x2e\ \x34\x39\x33\x2c\x31\x33\x37\x2e\x38\x30\x34\x2c\x35\x39\x2e\x33\ \x30\x36\x2c\x31\x36\x30\x2e\x39\x32\x35\x2c\x34\x39\x2e\x31\x30\ \x36\x7a\x20\x4d\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\ \x32\x33\x35\x63\x31\x2e\x31\x34\x38\x2c\x34\x2e\x35\x39\x32\x2d\ \x31\x2e\x36\x35\x33\x2c\x39\x2e\x32\x36\x32\x2d\x36\x2e\x32\x34\ \x31\x2c\x31\x30\x2e\x34\x31\x32\x20\x20\x20\x63\x2d\x30\x2e\x34\ \x34\x31\x2c\x30\x2e\x31\x32\x32\x2d\x34\x36\x2e\x30\x39\x38\x2c\ \x31\x32\x2e\x33\x39\x38\x2d\x35\x32\x2e\x30\x30\x38\x2c\x35\x33\ \x2e\x30\x33\x37\x63\x2d\x30\x2e\x36\x31\x32\x2c\x34\x2e\x31\x38\ \x39\x2d\x34\x2e\x32\x36\x35\x2c\x37\x2e\x33\x35\x35\x2d\x38\x2e\ \x34\x39\x34\x2c\x37\x2e\x33\x35\x35\x63\x2d\x30\x2e\x34\x31\x31\ \x2c\x30\x2d\x30\x2e\x38\x32\x38\x2d\x30\x2e\x30\x32\x34\x2d\x31\ \x2e\x32\x34\x35\x2d\x30\x2e\x30\x39\x38\x20\x20\x20\x63\x2d\x34\ \x2e\x36\x38\x33\x2d\x30\x2e\x36\x37\x36\x2d\x37\x2e\x39\x34\x36\ \x2d\x35\x2e\x30\x34\x38\x2d\x37\x2e\x32\x36\x35\x2d\x39\x2e\x37\ \x33\x36\x63\x37\x2e\x36\x31\x38\x2d\x35\x32\x2e\x33\x34\x39\x2c\ \x36\x32\x2e\x35\x30\x32\x2d\x36\x36\x2e\x36\x34\x36\x2c\x36\x34\ \x2e\x38\x33\x34\x2d\x36\x37\x2e\x32\x33\x63\x30\x2e\x36\x37\x39\ \x2d\x30\x2e\x31\x37\x2c\x31\x2e\x33\x38\x38\x2d\x30\x2e\x32\x35\ \x36\x2c\x32\x2e\x30\x39\x35\x2d\x30\x2e\x32\x35\x36\x20\x20\x20\ \x43\x31\x31\x31\x2e\x36\x32\x31\x2c\x32\x39\x31\x2e\x37\x33\x32\ \x2c\x31\x31\x35\x2e\x30\x34\x36\x2c\x32\x39\x34\x2e\x33\x39\x39\ \x2c\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\x32\x33\x35\ \x7a\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\x31\ \x34\x35\x63\x2d\x31\x2e\x34\x31\x39\x2c\x30\x2d\x32\x2e\x38\x30\ \x31\x2c\x30\x2e\x31\x35\x32\x2d\x34\x2e\x32\x30\x31\x2c\x30\x2e\ \x32\x31\x39\x20\x20\x20\x63\x32\x2e\x36\x34\x33\x2d\x31\x31\x2e\ \x39\x37\x31\x2c\x34\x2e\x31\x31\x2d\x32\x34\x2e\x35\x32\x37\x2c\ \x34\x2e\x31\x31\x2d\x33\x37\x2e\x35\x30\x33\x63\x30\x2d\x32\x32\ \x2e\x39\x34\x2d\x34\x2e\x32\x38\x37\x2d\x34\x34\x2e\x38\x38\x2d\ \x31\x32\x2e\x37\x33\x32\x2d\x36\x35\x2e\x32\x31\x35\x6c\x2d\x31\ \x2e\x34\x33\x38\x2d\x33\x2e\x34\x34\x39\x6c\x2d\x33\x2e\x33\x31\ \x38\x2c\x31\x2e\x37\x31\x37\x20\x20\x20\x63\x2d\x32\x31\x2e\x38\ \x39\x36\x2c\x31\x31\x2e\x33\x31\x37\x2d\x35\x37\x2e\x33\x30\x35\ \x2c\x31\x37\x2e\x38\x38\x31\x2d\x38\x33\x2e\x31\x34\x37\x2c\x32\ \x30\x2e\x32\x34\x37\x6c\x2d\x32\x2e\x34\x30\x35\x2c\x30\x2e\x32\ \x31\x36\x6c\x2d\x30\x2e\x36\x30\x33\x2c\x32\x2e\x33\x33\x38\x63\ \x2d\x37\x2e\x30\x32\x31\x2c\x32\x37\x2e\x30\x38\x38\x2d\x31\x31\ \x2e\x31\x36\x32\x2c\x36\x30\x2e\x33\x31\x33\x2d\x31\x33\x2e\x35\ \x39\x34\x2c\x39\x31\x2e\x31\x35\x35\x20\x20\x20\x63\x2d\x33\x2e\ \x38\x35\x31\x2d\x34\x39\x2e\x30\x34\x32\x2d\x39\x2e\x35\x30\x38\ \x2d\x39\x30\x2e\x36\x31\x33\x2d\x39\x2e\x36\x30\x33\x2d\x39\x31\ \x2e\x32\x34\x39\x6c\x2d\x30\x2e\x34\x32\x39\x2d\x32\x2e\x39\x34\ \x37\x63\x2d\x34\x35\x2e\x30\x33\x39\x2d\x34\x2e\x38\x32\x2d\x38\ \x33\x2e\x33\x36\x31\x2d\x32\x30\x2e\x33\x37\x32\x2d\x39\x30\x2e\ \x30\x38\x2d\x32\x33\x2e\x34\x33\x35\x6c\x2d\x33\x2e\x31\x35\x37\ \x2d\x31\x2e\x34\x34\x6c\x2d\x31\x2e\x34\x31\x36\x2c\x33\x2e\x31\ \x36\x39\x20\x20\x20\x63\x2d\x39\x2e\x33\x37\x32\x2c\x32\x31\x2e\ \x30\x32\x39\x2d\x31\x34\x2e\x33\x32\x32\x2c\x34\x34\x2e\x38\x35\ \x2d\x31\x34\x2e\x33\x32\x32\x2c\x36\x38\x2e\x38\x39\x33\x63\x30\ \x2c\x37\x39\x2e\x35\x37\x39\x2c\x35\x32\x2e\x39\x38\x38\x2c\x31\ \x34\x34\x2e\x33\x31\x39\x2c\x31\x31\x38\x2e\x31\x32\x2c\x31\x34\ \x34\x2e\x33\x31\x39\x63\x38\x2e\x36\x37\x35\x2c\x30\x2c\x31\x37\ \x2e\x31\x31\x2d\x31\x2e\x32\x33\x2c\x32\x35\x2e\x32\x34\x36\x2d\ \x33\x2e\x34\x31\x20\x20\x20\x63\x35\x2e\x30\x35\x34\x2c\x34\x36\ \x2e\x38\x33\x38\x2c\x34\x34\x2e\x38\x30\x34\x2c\x38\x33\x2e\x34\ \x34\x35\x2c\x39\x32\x2e\x39\x36\x33\x2c\x38\x33\x2e\x34\x34\x35\ \x63\x35\x31\x2e\x35\x36\x39\x2c\x30\x2c\x39\x33\x2e\x35\x32\x39\ \x2d\x34\x31\x2e\x39\x36\x2c\x39\x33\x2e\x35\x32\x39\x2d\x39\x33\ \x2e\x35\x32\x39\x43\x34\x31\x32\x2e\x37\x36\x2c\x32\x36\x34\x2e\ \x31\x31\x32\x2c\x33\x37\x30\x2e\x38\x31\x32\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x7a\x20\x20\x20\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\ \x2c\x34\x30\x32\x2e\x39\x36\x38\x63\x2d\x34\x38\x2e\x31\x33\x35\ \x2c\x30\x2d\x38\x37\x2e\x32\x39\x34\x2d\x33\x39\x2e\x31\x35\x33\ \x2d\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\x32\x39\x34\x63\x30\ \x2d\x34\x38\x2e\x31\x32\x39\x2c\x33\x39\x2e\x31\x35\x39\x2d\x38\ \x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\ \x32\x39\x34\x73\x38\x37\x2e\x32\x39\x34\x2c\x33\x39\x2e\x31\x36\ \x35\x2c\x38\x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x20\ \x20\x20\x43\x34\x30\x36\x2e\x35\x33\x2c\x33\x36\x33\x2e\x38\x31\ \x35\x2c\x33\x36\x37\x2e\x33\x36\x35\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\x46\x46\ \x46\x22\x2f\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x2f\x73\x76\x67\x3e\x0a\ -\x00\x00\x03\x68\ +\x00\x00\x04\x03\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ -\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\x2e\ +\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ -\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x78\ -\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ -\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\ -\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\ -\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\ -\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x76\x65\x72\ -\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\x3d\x22\x43\ -\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\x22\x20\x79\ -\x3d\x22\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ -\x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ -\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ -\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ -\x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ -\x39\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ -\x72\x65\x73\x65\x72\x76\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ -\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\ -\x31\x32\x70\x78\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\ -\x4d\x33\x36\x2e\x30\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x6c\x2d\ -\x32\x39\x2d\x32\x30\x43\x36\x2e\x37\x36\x31\x2d\x30\x2e\x30\x33\ -\x35\x2c\x36\x2e\x33\x36\x33\x2d\x30\x2e\x30\x35\x37\x2c\x36\x2e\ -\x30\x33\x35\x2c\x30\x2e\x31\x31\x34\x43\x35\x2e\x37\x30\x36\x2c\ -\x30\x2e\x32\x38\x37\x2c\x35\x2e\x35\x2c\x30\x2e\x36\x32\x37\x2c\ -\x35\x2e\x35\x2c\x30\x2e\x39\x39\x39\x76\x34\x30\x20\x20\x63\x30\ -\x2c\x30\x2e\x33\x37\x32\x2c\x30\x2e\x32\x30\x36\x2c\x30\x2e\x37\ -\x31\x33\x2c\x30\x2e\x35\x33\x35\x2c\x30\x2e\x38\x38\x36\x63\x30\ -\x2e\x31\x34\x36\x2c\x30\x2e\x30\x37\x36\x2c\x30\x2e\x33\x30\x36\ -\x2c\x30\x2e\x31\x31\x34\x2c\x30\x2e\x34\x36\x35\x2c\x30\x2e\x31\ -\x31\x34\x63\x30\x2e\x31\x39\x39\x2c\x30\x2c\x30\x2e\x33\x39\x37\ -\x2d\x30\x2e\x30\x36\x2c\x30\x2e\x35\x36\x38\x2d\x30\x2e\x31\x37\ -\x37\x6c\x32\x39\x2d\x32\x30\x20\x20\x63\x30\x2e\x32\x37\x31\x2d\ -\x30\x2e\x31\x38\x37\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x34\x39\ -\x34\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x38\x32\x33\x53\x33\x36\ -\x2e\x33\x33\x38\x2c\x32\x30\x2e\x33\x36\x33\x2c\x33\x36\x2e\x30\ -\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x7a\x20\x4d\x37\x2e\x35\x2c\ -\x33\x39\x2e\x30\x39\x35\x56\x32\x2e\x39\x30\x34\x6c\x32\x36\x2e\ -\x32\x33\x39\x2c\x31\x38\x2e\x30\x39\x36\x4c\x37\x2e\x35\x2c\x33\ -\x39\x2e\x30\x39\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\ -\x46\x46\x46\x46\x46\x22\x2f\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ -\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ +\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ +\x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ +\x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ +\x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ +\x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ +\x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ +\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ +\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ +\x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ +\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ +\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ +\x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ +\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\x38\x2e\ +\x38\x33\x32\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\ +\x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\ +\x77\x20\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\ +\x38\x2e\x38\x33\x32\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\ +\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\ +\x3e\x0a\x09\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x36\x35\ +\x2e\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x6c\x2d\x38\x30\ +\x2d\x38\x30\x63\x2d\x34\x2e\x38\x38\x31\x2d\x34\x2e\x38\x38\x31\ +\x2d\x31\x32\x2e\x37\x39\x37\x2d\x34\x2e\x38\x38\x31\x2d\x31\x37\ +\x2e\x36\x37\x38\x2c\x30\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\ +\x38\x38\x32\x2d\x34\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\ +\x2c\x30\x2c\x31\x37\x2e\x36\x37\x38\x6c\x35\x38\x2e\x36\x36\x31\ +\x2c\x35\x38\x2e\x36\x36\x31\x48\x31\x32\x2e\x35\x20\x20\x20\x63\ +\x2d\x36\x2e\x39\x30\x33\x2c\x30\x2d\x31\x32\x2e\x35\x2c\x35\x2e\ +\x35\x39\x37\x2d\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x63\x30\x2c\ +\x36\x2e\x39\x30\x32\x2c\x35\x2e\x35\x39\x37\x2c\x31\x32\x2e\x35\ +\x2c\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x68\x32\x31\x33\x2e\x36\ +\x35\x34\x6c\x2d\x35\x38\x2e\x36\x35\x39\x2c\x35\x38\x2e\x36\x36\ +\x31\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\x38\x38\x32\x2d\x34\ +\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\x2c\x30\x2c\x31\x37\ +\x2e\x36\x37\x38\x20\x20\x20\x63\x32\x2e\x34\x34\x2c\x32\x2e\x34\ +\x33\x39\x2c\x35\x2e\x36\x34\x2c\x33\x2e\x36\x36\x31\x2c\x38\x2e\ +\x38\x33\x39\x2c\x33\x2e\x36\x36\x31\x73\x36\x2e\x33\x39\x38\x2d\ +\x31\x2e\x32\x32\x32\x2c\x38\x2e\x38\x33\x39\x2d\x33\x2e\x36\x36\ +\x31\x6c\x37\x39\x2e\x39\x39\x38\x2d\x38\x30\x43\x32\x37\x30\x2e\ +\x30\x35\x33\x2c\x31\x33\x38\x2e\x33\x37\x33\x2c\x32\x37\x30\x2e\ +\x30\x35\x33\x2c\x31\x33\x30\x2e\x34\x35\x39\x2c\x32\x36\x35\x2e\ +\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x7a\x22\x20\x66\x69\ +\x6c\x6c\x3d\x22\x23\x46\x46\x44\x41\x34\x34\x22\x2f\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ -\x3c\x2f\x73\x76\x67\x3e\x0a\ +\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ +\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\ +\x3e\x0a\ +\x00\x00\x10\xdf\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\ +\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\ +\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\ +\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\ +\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\ +\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\ +\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\ +\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\ +\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\ +\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\ +\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\ +\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\ +\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ +\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\ +\x22\x0a\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\x31\x32\ +\x70\x78\x22\x0a\x20\x20\x20\x77\x69\x64\x74\x68\x3d\x22\x35\x31\ +\x32\x70\x78\x22\x0a\x20\x20\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\ +\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x0a\x20\x20\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\ +\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\x30\x20\x30\ +\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\x39\x3b\x22\ +\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\ +\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\x39\x22\x0a\ +\x20\x20\x20\x79\x3d\x22\x30\x70\x78\x22\x0a\x20\x20\x20\x78\x3d\ +\x22\x30\x70\x78\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x43\x61\x70\ +\x61\x5f\x31\x22\x0a\x20\x20\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\ +\x22\x31\x2e\x31\x22\x3e\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x0a\ +\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x33\x39\x22\x3e\x3c\x72\x64\x66\x3a\x52\x44\x46\x3e\x3c\x63\ +\x63\x3a\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x72\x64\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x3c\x64\x63\ +\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x69\x6d\x61\x67\x65\x2f\x73\x76\ +\x67\x2b\x78\x6d\x6c\x3c\x2f\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\ +\x3e\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\x72\x63\ +\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\x2e\x6f\ +\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\x2f\x53\ +\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x3c\x64\x63\ +\x3a\x74\x69\x74\x6c\x65\x3e\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\ +\x65\x3e\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x3c\x2f\x72\x64\ +\x66\x3a\x52\x44\x46\x3e\x3c\x2f\x6d\x65\x74\x61\x64\x61\x74\x61\ +\x3e\x3c\x64\x65\x66\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\ +\x64\x65\x66\x73\x33\x37\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\ +\x20\x20\x69\x64\x3d\x22\x67\x34\x22\x20\x2f\x3e\x3c\x67\x0a\x20\ +\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x36\x22\x20\x2f\x3e\x3c\x67\ +\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x38\x22\x20\x2f\x3e\ +\x3c\x67\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x30\x22\ +\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\ +\x31\x32\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x69\x64\ +\x3d\x22\x67\x31\x34\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\ +\x20\x69\x64\x3d\x22\x67\x31\x36\x22\x20\x2f\x3e\x3c\x67\x0a\x20\ +\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x38\x22\x20\x2f\x3e\x3c\ +\x67\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x32\x30\x22\x20\ +\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x32\ +\x32\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\ +\x22\x67\x32\x34\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\ +\x69\x64\x3d\x22\x67\x32\x36\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\ +\x20\x20\x20\x69\x64\x3d\x22\x67\x32\x38\x22\x20\x2f\x3e\x3c\x67\ +\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x33\x30\x22\x20\x2f\ +\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x33\x32\ +\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x72\x6f\x6b\x65\x3a\x6e\x6f\x6e\x65\x3b\x66\ +\x69\x6c\x6c\x3a\x23\x66\x66\x66\x66\x66\x66\x3b\x66\x69\x6c\x6c\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x22\x0a\x20\x20\x20\x20\ +\x20\x69\x64\x3d\x22\x6c\x61\x79\x65\x72\x31\x22\x0a\x20\x20\x20\ +\x20\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\ +\x72\x69\x78\x28\x32\x2e\x36\x32\x34\x39\x33\x37\x35\x2c\x30\x2c\ +\x30\x2c\x32\x2e\x36\x32\x34\x39\x33\x37\x35\x2c\x2d\x37\x2e\x38\ +\x37\x34\x38\x31\x32\x35\x2c\x2d\x32\x37\x31\x32\x2e\x35\x31\x31\ +\x32\x29\x22\x3e\x3c\x70\x61\x74\x68\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x66\x66\ +\x66\x66\x66\x66\x3b\x66\x69\x6c\x6c\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x6e\x6f\x6e\x65\x22\ +\x0a\x20\x20\x20\x20\x20\x20\x20\x64\x3d\x22\x6d\x20\x34\x2c\x33\ +\x20\x76\x20\x31\x36\x20\x68\x20\x31\x20\x38\x20\x31\x20\x63\x20\ +\x31\x2e\x33\x37\x34\x37\x36\x35\x2c\x2d\x31\x2e\x33\x35\x37\x39\ +\x39\x33\x20\x32\x2e\x37\x37\x39\x32\x30\x39\x2c\x2d\x32\x2e\x37\ +\x36\x38\x33\x31\x34\x20\x34\x2c\x2d\x34\x20\x56\x20\x31\x33\x2e\ +\x35\x39\x33\x37\x35\x20\x33\x20\x48\x20\x31\x37\x20\x35\x20\x5a\ +\x20\x6d\x20\x30\x2e\x36\x32\x35\x2c\x30\x2e\x36\x32\x35\x30\x30\ +\x33\x39\x20\x68\x20\x31\x32\x2e\x37\x35\x20\x56\x20\x31\x34\x20\ +\x31\x35\x2e\x30\x30\x30\x30\x30\x34\x20\x48\x20\x31\x34\x20\x76\ +\x20\x31\x2e\x31\x38\x37\x35\x20\x32\x2e\x31\x38\x37\x35\x20\x48\ +\x20\x31\x33\x20\x34\x2e\x36\x32\x35\x20\x5a\x22\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x74\ +\x72\x61\x6e\x73\x6c\x61\x74\x65\x28\x30\x2c\x31\x30\x33\x30\x2e\ +\x33\x36\x32\x32\x29\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\ +\x3d\x22\x72\x65\x63\x74\x34\x31\x33\x39\x22\x20\x2f\x3e\x3c\x2f\ +\x67\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x66\x69\x6c\x6c\x3a\x23\x66\x66\x66\x66\x66\x66\x3b\x66\x69\ +\x6c\x6c\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x73\x74\x72\ +\x6f\x6b\x65\x3a\x6e\x6f\x6e\x65\x22\x0a\x20\x20\x20\x20\x20\x69\ +\x64\x3d\x22\x6c\x61\x79\x65\x72\x31\x2d\x37\x22\x0a\x20\x20\x20\ +\x20\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\ +\x72\x69\x78\x28\x31\x2e\x35\x33\x38\x30\x34\x39\x33\x2c\x30\x2c\ +\x30\x2c\x31\x2e\x35\x33\x38\x30\x34\x39\x33\x2c\x34\x2e\x30\x38\ +\x30\x39\x35\x37\x37\x2c\x2d\x31\x35\x38\x30\x2e\x36\x36\x36\x39\ +\x29\x22\x3e\x3c\x70\x61\x74\x68\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x66\x66\x66\ +\x66\x66\x66\x3b\x66\x69\x6c\x6c\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x6e\x6f\x6e\x65\x22\x0a\ +\x20\x20\x20\x20\x20\x20\x20\x64\x3d\x22\x6d\x20\x31\x31\x2c\x31\ +\x30\x34\x39\x2e\x33\x36\x32\x32\x20\x63\x20\x2d\x34\x2e\x34\x33\ +\x31\x39\x36\x36\x2c\x30\x20\x2d\x38\x2c\x2d\x33\x2e\x35\x36\x38\ +\x20\x2d\x38\x2c\x2d\x38\x20\x30\x2c\x2d\x31\x2e\x34\x34\x31\x37\ +\x20\x30\x2e\x33\x38\x32\x38\x37\x31\x2c\x2d\x32\x2e\x37\x38\x39\ +\x33\x20\x31\x2e\x30\x34\x34\x39\x32\x32\x2c\x2d\x33\x2e\x39\x35\ +\x35\x31\x20\x6c\x20\x30\x2e\x37\x33\x38\x32\x38\x31\x2c\x30\x2e\ +\x37\x33\x38\x33\x20\x43\x20\x34\x2e\x32\x38\x35\x38\x32\x39\x2c\ +\x31\x30\x33\x39\x2e\x31\x30\x38\x33\x20\x34\x2c\x31\x30\x34\x30\ +\x2e\x31\x39\x39\x37\x20\x34\x2c\x31\x30\x34\x31\x2e\x33\x36\x32\ +\x32\x20\x63\x20\x30\x2c\x33\x2e\x38\x37\x38\x20\x33\x2e\x31\x32\ +\x32\x30\x33\x2c\x37\x20\x37\x2c\x37\x20\x31\x2e\x31\x36\x32\x35\ +\x30\x39\x2c\x30\x20\x32\x2e\x32\x35\x33\x38\x39\x2c\x2d\x30\x2e\ +\x32\x38\x35\x38\x20\x33\x2e\x32\x31\x36\x37\x39\x37\x2c\x2d\x30\ +\x2e\x37\x38\x33\x32\x20\x6c\x20\x30\x2e\x37\x33\x38\x32\x38\x31\ +\x2c\x30\x2e\x37\x33\x38\x33\x20\x63\x20\x2d\x31\x2e\x31\x36\x35\ +\x37\x38\x39\x2c\x30\x2e\x36\x36\x32\x20\x2d\x32\x2e\x35\x31\x33\ +\x33\x36\x34\x2c\x31\x2e\x30\x34\x34\x39\x20\x2d\x33\x2e\x39\x35\ +\x35\x30\x37\x38\x2c\x31\x2e\x30\x34\x34\x39\x20\x7a\x20\x6d\x20\ +\x36\x2e\x39\x35\x35\x30\x37\x38\x2c\x2d\x34\x2e\x30\x34\x34\x39\ +\x20\x2d\x30\x2e\x37\x33\x38\x32\x38\x31\x2c\x2d\x30\x2e\x37\x33\ +\x38\x33\x20\x43\x20\x31\x37\x2e\x37\x31\x34\x31\x37\x32\x2c\x31\ +\x30\x34\x33\x2e\x36\x31\x36\x31\x20\x31\x38\x2c\x31\x30\x34\x32\ +\x2e\x35\x32\x34\x37\x20\x31\x38\x2c\x31\x30\x34\x31\x2e\x33\x36\ +\x32\x32\x20\x63\x20\x30\x2c\x2d\x33\x2e\x38\x37\x38\x20\x2d\x33\ +\x2e\x31\x32\x32\x30\x33\x2c\x2d\x37\x20\x2d\x37\x2c\x2d\x37\x20\ +\x2d\x31\x2e\x31\x36\x32\x35\x31\x2c\x30\x20\x2d\x32\x2e\x32\x35\ +\x33\x38\x39\x2c\x30\x2e\x32\x38\x35\x38\x20\x2d\x33\x2e\x32\x31\ +\x36\x37\x39\x37\x2c\x30\x2e\x37\x38\x33\x32\x20\x6c\x20\x2d\x30\ +\x2e\x37\x33\x38\x32\x38\x31\x2c\x2d\x30\x2e\x37\x33\x38\x33\x20\ +\x63\x20\x31\x2e\x31\x36\x35\x37\x38\x39\x2c\x2d\x30\x2e\x36\x36\ +\x32\x20\x32\x2e\x35\x31\x33\x33\x36\x34\x2c\x2d\x31\x2e\x30\x34\ +\x34\x39\x20\x33\x2e\x39\x35\x35\x30\x37\x38\x2c\x2d\x31\x2e\x30\ +\x34\x34\x39\x20\x34\x2e\x34\x33\x31\x39\x36\x36\x2c\x30\x20\x38\ +\x2c\x33\x2e\x35\x36\x38\x20\x38\x2c\x38\x20\x30\x2c\x31\x2e\x34\ +\x34\x31\x37\x20\x2d\x30\x2e\x33\x38\x32\x38\x37\x31\x2c\x32\x2e\ +\x37\x38\x39\x33\x20\x2d\x31\x2e\x30\x34\x34\x39\x32\x32\x2c\x33\ +\x2e\x39\x35\x35\x31\x20\x7a\x22\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x69\x64\x3d\x22\x72\x65\x63\x74\x34\x30\x38\x35\x22\x20\x2f\x3e\ +\x3c\x72\x65\x63\x74\x0a\x20\x20\x20\x20\x20\x20\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x63\x6f\x6c\x6f\x72\x3a\x23\x30\x30\x30\x30\x30\ +\x30\x3b\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\x72\x61\x74\x69\x6f\ +\x6e\x3a\x6e\x6f\x6e\x65\x3b\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\ +\x72\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x6e\x65\x3a\x6e\x6f\x6e\x65\ +\x3b\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\ +\x2d\x73\x74\x79\x6c\x65\x3a\x73\x6f\x6c\x69\x64\x3b\x74\x65\x78\ +\x74\x2d\x64\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x30\x30\x30\x30\x30\x30\x3b\x63\x6c\x69\x70\x2d\ +\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x3b\x64\x69\x73\ +\x70\x6c\x61\x79\x3a\x69\x6e\x6c\x69\x6e\x65\x3b\x6f\x76\x65\x72\ +\x66\x6c\x6f\x77\x3a\x76\x69\x73\x69\x62\x6c\x65\x3b\x76\x69\x73\ +\x69\x62\x69\x6c\x69\x74\x79\x3a\x76\x69\x73\x69\x62\x6c\x65\x3b\ +\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x69\x73\x6f\x6c\x61\x74\ +\x69\x6f\x6e\x3a\x61\x75\x74\x6f\x3b\x6d\x69\x78\x2d\x62\x6c\x65\ +\x6e\x64\x2d\x6d\x6f\x64\x65\x3a\x6e\x6f\x72\x6d\x61\x6c\x3b\x63\ +\x6f\x6c\x6f\x72\x2d\x69\x6e\x74\x65\x72\x70\x6f\x6c\x61\x74\x69\ +\x6f\x6e\x3a\x73\x52\x47\x42\x3b\x63\x6f\x6c\x6f\x72\x2d\x69\x6e\ +\x74\x65\x72\x70\x6f\x6c\x61\x74\x69\x6f\x6e\x2d\x66\x69\x6c\x74\ +\x65\x72\x73\x3a\x6c\x69\x6e\x65\x61\x72\x52\x47\x42\x3b\x73\x6f\ +\x6c\x69\x64\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x30\x30\x30\x30\x30\ +\x30\x3b\x73\x6f\x6c\x69\x64\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x66\x69\x6c\x6c\x3a\x23\x66\x66\x66\x66\x66\x66\x3b\x66\ +\x69\x6c\x6c\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x66\x69\ +\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x3b\ +\x73\x74\x72\x6f\x6b\x65\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\ +\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x62\x75\x74\x74\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x6d\ +\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\ +\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\x65\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x64\x61\x73\x68\x6f\x66\x66\x73\x65\x74\ +\x3a\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x63\x6f\x6c\x6f\x72\x2d\x72\x65\x6e\x64\x65\x72\ +\x69\x6e\x67\x3a\x61\x75\x74\x6f\x3b\x69\x6d\x61\x67\x65\x2d\x72\ +\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x61\x75\x74\x6f\x3b\x73\x68\ +\x61\x70\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x61\x75\ +\x74\x6f\x3b\x74\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x61\x75\x74\x6f\x3b\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\ +\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x61\x63\x63\x75\x6d\x75\x6c\ +\x61\x74\x65\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\ +\x72\x65\x63\x74\x34\x33\x33\x36\x22\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x31\x22\x0a\x20\x20\x20\x20\x20\ +\x20\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x34\x22\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x78\x3d\x22\x2d\x37\x32\x36\x2e\x34\x35\x31\x31\ +\x37\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x79\x3d\x22\x37\x33\x37\ +\x2e\x33\x39\x30\x35\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x74\x72\ +\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x72\x6f\x74\x61\x74\x65\x28\ +\x2d\x34\x35\x29\x22\x20\x2f\x3e\x3c\x72\x65\x63\x74\x0a\x20\x20\ +\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x30\x30\x30\x30\x30\x30\x3b\x74\x65\x78\x74\x2d\x64\ +\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\x3a\x6e\x6f\x6e\x65\x3b\x74\ +\x65\x78\x74\x2d\x64\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\x2d\x6c\ +\x69\x6e\x65\x3a\x6e\x6f\x6e\x65\x3b\x74\x65\x78\x74\x2d\x64\x65\ +\x63\x6f\x72\x61\x74\x69\x6f\x6e\x2d\x73\x74\x79\x6c\x65\x3a\x73\ +\x6f\x6c\x69\x64\x3b\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\x72\x61\ +\x74\x69\x6f\x6e\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x30\x30\x30\x30\ +\x30\x30\x3b\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\ +\x7a\x65\x72\x6f\x3b\x64\x69\x73\x70\x6c\x61\x79\x3a\x69\x6e\x6c\ +\x69\x6e\x65\x3b\x6f\x76\x65\x72\x66\x6c\x6f\x77\x3a\x76\x69\x73\ +\x69\x62\x6c\x65\x3b\x76\x69\x73\x69\x62\x69\x6c\x69\x74\x79\x3a\ +\x76\x69\x73\x69\x62\x6c\x65\x3b\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x69\x73\x6f\x6c\x61\x74\x69\x6f\x6e\x3a\x61\x75\x74\x6f\ +\x3b\x6d\x69\x78\x2d\x62\x6c\x65\x6e\x64\x2d\x6d\x6f\x64\x65\x3a\ +\x6e\x6f\x72\x6d\x61\x6c\x3b\x63\x6f\x6c\x6f\x72\x2d\x69\x6e\x74\ +\x65\x72\x70\x6f\x6c\x61\x74\x69\x6f\x6e\x3a\x73\x52\x47\x42\x3b\ +\x63\x6f\x6c\x6f\x72\x2d\x69\x6e\x74\x65\x72\x70\x6f\x6c\x61\x74\ +\x69\x6f\x6e\x2d\x66\x69\x6c\x74\x65\x72\x73\x3a\x6c\x69\x6e\x65\ +\x61\x72\x52\x47\x42\x3b\x73\x6f\x6c\x69\x64\x2d\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x30\x30\x30\x30\x30\x30\x3b\x73\x6f\x6c\x69\x64\x2d\ +\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x66\x69\x6c\x6c\x3a\x23\ +\x66\x66\x66\x66\x66\x66\x3b\x66\x69\x6c\x6c\x2d\x6f\x70\x61\x63\ +\x69\x74\x79\x3a\x31\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\ +\x6e\x6f\x6e\x7a\x65\x72\x6f\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x6e\ +\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\ +\x3a\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\ +\x70\x3a\x62\x75\x74\x74\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\ +\x6e\x65\x6a\x6f\x69\x6e\x3a\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x64\x61\x73\x68\x61\x72\x72\x61\ +\x79\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x64\x61\ +\x73\x68\x6f\x66\x66\x73\x65\x74\x3a\x30\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x63\x6f\x6c\x6f\ +\x72\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x61\x75\x74\x6f\ +\x3b\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\ +\x3a\x61\x75\x74\x6f\x3b\x73\x68\x61\x70\x65\x2d\x72\x65\x6e\x64\ +\x65\x72\x69\x6e\x67\x3a\x61\x75\x74\x6f\x3b\x74\x65\x78\x74\x2d\ +\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x61\x75\x74\x6f\x3b\x65\ +\x6e\x61\x62\x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\ +\x3a\x61\x63\x63\x75\x6d\x75\x6c\x61\x74\x65\x22\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x69\x64\x3d\x22\x72\x65\x63\x74\x34\x33\x33\x36\ +\x2d\x35\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x77\x69\x64\x74\x68\ +\x3d\x22\x31\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x68\x65\x69\x67\ +\x68\x74\x3d\x22\x34\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x78\x3d\ +\x22\x2d\x37\x33\x31\x2e\x36\x39\x35\x30\x37\x22\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x79\x3d\x22\x37\x34\x36\x2e\x38\x38\x30\x33\x37\ +\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x74\x72\x61\x6e\x73\x66\x6f\ +\x72\x6d\x3d\x22\x72\x6f\x74\x61\x74\x65\x28\x2d\x34\x35\x29\x22\ +\x20\x2f\x3e\x3c\x2f\x67\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x74\ +\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x74\x72\x61\x6e\x73\x6c\ +\x61\x74\x65\x28\x2d\x31\x2e\x35\x31\x35\x39\x38\x39\x39\x2c\x31\ +\x31\x2e\x36\x32\x33\x35\x30\x32\x29\x22\x0a\x20\x20\x20\x20\x20\ +\x69\x64\x3d\x22\x67\x31\x31\x35\x33\x22\x3e\x3c\x67\x0a\x20\x20\ +\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x30\x30\x22\x20\ +\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\ +\x67\x31\x31\x30\x32\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\ +\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x30\x34\x22\x20\x2f\x3e\ +\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\ +\x31\x30\x36\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x69\x64\x3d\x22\x67\x31\x31\x30\x38\x22\x20\x2f\x3e\x3c\x67\ +\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x31\ +\x30\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\x20\x69\ +\x64\x3d\x22\x67\x31\x31\x31\x32\x22\x20\x2f\x3e\x3c\x67\x0a\x20\ +\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x31\x34\x22\ +\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\ +\x22\x67\x31\x31\x31\x36\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x31\x38\x22\x20\x2f\ +\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\ +\x31\x31\x32\x30\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\ +\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x32\x32\x22\x20\x2f\x3e\x3c\ +\x67\x0a\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\ +\x32\x34\x22\x20\x2f\x3e\x3c\x67\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x69\x64\x3d\x22\x67\x31\x31\x32\x36\x22\x20\x2f\x3e\x3c\x67\x0a\ +\x20\x20\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x67\x31\x31\x32\x38\ +\x22\x20\x2f\x3e\x3c\x2f\x67\x3e\x3c\x2f\x73\x76\x67\x3e\ \x00\x00\x02\xe2\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\ \x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\ \x65\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\ \x2e\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\ \x20\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\ \x65\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\ \x6c\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\ \x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\ \x64\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ \x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ \x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x76\x69\x65\x77\ \x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x35\x38\x20\x35\x38\x22\x20\ \x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\ \x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\x30\x20\x30\ \x20\x35\x38\x20\x35\x38\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\ \x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0d\x0a\ \x3c\x63\x69\x72\x63\x6c\x65\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\ \x69\x6c\x6c\x3a\x23\x44\x37\x35\x41\x34\x41\x3b\x22\x20\x63\x78\ \x3d\x22\x32\x39\x22\x20\x63\x79\x3d\x22\x32\x39\x22\x20\x72\x3d\ \x22\x32\x39\x22\x2f\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x09\x3c\x72\ \x65\x63\x74\x20\x78\x3d\x22\x31\x36\x22\x20\x79\x3d\x22\x31\x36\ \x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x46\ \x46\x46\x46\x46\x46\x3b\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ \x36\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x36\x22\x2f\x3e\ \x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x73\x74\x79\x6c\x65\x3d\x22\ \x66\x69\x6c\x6c\x3a\x23\x46\x46\x46\x46\x46\x46\x3b\x22\x20\x64\ \x3d\x22\x4d\x34\x33\x2c\x34\x33\x48\x31\x35\x56\x31\x35\x68\x32\ \x38\x56\x34\x33\x7a\x20\x4d\x31\x37\x2c\x34\x31\x68\x32\x34\x56\ \x31\x37\x48\x31\x37\x56\x34\x31\x7a\x22\x2f\x3e\x0d\x0a\x3c\x2f\ \x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\ \x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\ \x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\ \x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\ \x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\ \x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\ \x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\ \x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\ \x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\ \x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\ \x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ \x0a\ -\x00\x00\x04\x03\ +\x00\x00\x03\x68\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ -\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ +\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ -\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ -\x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ -\x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ -\x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ -\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ -\x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ -\x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ -\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ -\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ -\x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ -\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ -\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ -\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ -\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ -\x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ -\x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ -\x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ -\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\x38\x2e\ -\x38\x33\x32\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\ -\x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\ -\x77\x20\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\ -\x38\x2e\x38\x33\x32\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\ -\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\ -\x3e\x0a\x09\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x36\x35\ -\x2e\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x6c\x2d\x38\x30\ -\x2d\x38\x30\x63\x2d\x34\x2e\x38\x38\x31\x2d\x34\x2e\x38\x38\x31\ -\x2d\x31\x32\x2e\x37\x39\x37\x2d\x34\x2e\x38\x38\x31\x2d\x31\x37\ -\x2e\x36\x37\x38\x2c\x30\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\ -\x38\x38\x32\x2d\x34\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\ -\x2c\x30\x2c\x31\x37\x2e\x36\x37\x38\x6c\x35\x38\x2e\x36\x36\x31\ -\x2c\x35\x38\x2e\x36\x36\x31\x48\x31\x32\x2e\x35\x20\x20\x20\x63\ -\x2d\x36\x2e\x39\x30\x33\x2c\x30\x2d\x31\x32\x2e\x35\x2c\x35\x2e\ -\x35\x39\x37\x2d\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x63\x30\x2c\ -\x36\x2e\x39\x30\x32\x2c\x35\x2e\x35\x39\x37\x2c\x31\x32\x2e\x35\ -\x2c\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x68\x32\x31\x33\x2e\x36\ -\x35\x34\x6c\x2d\x35\x38\x2e\x36\x35\x39\x2c\x35\x38\x2e\x36\x36\ -\x31\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\x38\x38\x32\x2d\x34\ -\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\x2c\x30\x2c\x31\x37\ -\x2e\x36\x37\x38\x20\x20\x20\x63\x32\x2e\x34\x34\x2c\x32\x2e\x34\ -\x33\x39\x2c\x35\x2e\x36\x34\x2c\x33\x2e\x36\x36\x31\x2c\x38\x2e\ -\x38\x33\x39\x2c\x33\x2e\x36\x36\x31\x73\x36\x2e\x33\x39\x38\x2d\ -\x31\x2e\x32\x32\x32\x2c\x38\x2e\x38\x33\x39\x2d\x33\x2e\x36\x36\ -\x31\x6c\x37\x39\x2e\x39\x39\x38\x2d\x38\x30\x43\x32\x37\x30\x2e\ -\x30\x35\x33\x2c\x31\x33\x38\x2e\x33\x37\x33\x2c\x32\x37\x30\x2e\ -\x30\x35\x33\x2c\x31\x33\x30\x2e\x34\x35\x39\x2c\x32\x36\x35\x2e\ -\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x7a\x22\x20\x66\x69\ -\x6c\x6c\x3d\x22\x23\x46\x46\x44\x41\x34\x34\x22\x2f\x3e\x0a\x3c\ +\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x78\ +\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ +\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\ +\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\ +\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\ +\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x76\x65\x72\ +\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\x3d\x22\x43\ +\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\x22\x20\x79\ +\x3d\x22\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ +\x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ +\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ +\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ +\x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ +\x39\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ +\x72\x65\x73\x65\x72\x76\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\ +\x31\x32\x70\x78\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\ +\x4d\x33\x36\x2e\x30\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x6c\x2d\ +\x32\x39\x2d\x32\x30\x43\x36\x2e\x37\x36\x31\x2d\x30\x2e\x30\x33\ +\x35\x2c\x36\x2e\x33\x36\x33\x2d\x30\x2e\x30\x35\x37\x2c\x36\x2e\ +\x30\x33\x35\x2c\x30\x2e\x31\x31\x34\x43\x35\x2e\x37\x30\x36\x2c\ +\x30\x2e\x32\x38\x37\x2c\x35\x2e\x35\x2c\x30\x2e\x36\x32\x37\x2c\ +\x35\x2e\x35\x2c\x30\x2e\x39\x39\x39\x76\x34\x30\x20\x20\x63\x30\ +\x2c\x30\x2e\x33\x37\x32\x2c\x30\x2e\x32\x30\x36\x2c\x30\x2e\x37\ +\x31\x33\x2c\x30\x2e\x35\x33\x35\x2c\x30\x2e\x38\x38\x36\x63\x30\ +\x2e\x31\x34\x36\x2c\x30\x2e\x30\x37\x36\x2c\x30\x2e\x33\x30\x36\ +\x2c\x30\x2e\x31\x31\x34\x2c\x30\x2e\x34\x36\x35\x2c\x30\x2e\x31\ +\x31\x34\x63\x30\x2e\x31\x39\x39\x2c\x30\x2c\x30\x2e\x33\x39\x37\ +\x2d\x30\x2e\x30\x36\x2c\x30\x2e\x35\x36\x38\x2d\x30\x2e\x31\x37\ +\x37\x6c\x32\x39\x2d\x32\x30\x20\x20\x63\x30\x2e\x32\x37\x31\x2d\ +\x30\x2e\x31\x38\x37\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x34\x39\ +\x34\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x38\x32\x33\x53\x33\x36\ +\x2e\x33\x33\x38\x2c\x32\x30\x2e\x33\x36\x33\x2c\x33\x36\x2e\x30\ +\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x7a\x20\x4d\x37\x2e\x35\x2c\ +\x33\x39\x2e\x30\x39\x35\x56\x32\x2e\x39\x30\x34\x6c\x32\x36\x2e\ +\x32\x33\x39\x2c\x31\x38\x2e\x30\x39\x36\x4c\x37\x2e\x35\x2c\x33\ +\x39\x2e\x30\x39\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\ +\x46\x46\x46\x46\x46\x22\x2f\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ +\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ -\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ -\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\ -\x3e\x0a\ -\x00\x00\x03\x58\ -\x3c\ -\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ -\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\ -\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\ -\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\ -\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\x30\x2e\x30\x2c\ -\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\ -\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\ -\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\ -\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ -\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ -\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ -\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ -\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ -\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ -\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\ -\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ -\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ -\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ -\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ -\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ -\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ -\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ -\x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\x74\ -\x68\x3d\x22\x32\x34\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ -\x22\x32\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ -\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x65\x6e\x61\x62\x6c\ -\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\x22\x6e\x65\ -\x77\x20\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x78\x6d\x6c\ -\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\ -\x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x39\x2e\ -\x34\x38\x38\x2c\x33\x2e\x39\x30\x37\x48\x32\x34\x76\x32\x2e\x37\ -\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x33\x2e\x39\x30\x37\x7a\x20\ -\x4d\x31\x31\x2e\x31\x36\x33\x2c\x38\x2e\x33\x37\x32\x48\x32\x34\ -\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\x31\x36\x33\x56\x38\x2e\ -\x33\x37\x32\x7a\x20\x4d\x31\x31\x2e\x31\x36\x33\x2c\x31\x32\x2e\ -\x38\x33\x37\x48\x32\x34\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\ -\x31\x36\x33\x56\x31\x32\x2e\x38\x33\x37\x7a\x0d\x0a\x09\x20\x4d\ -\x39\x2e\x34\x38\x38\x2c\x31\x37\x2e\x33\x30\x32\x48\x32\x34\x76\ -\x32\x2e\x37\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x31\x37\x2e\x33\ -\x30\x32\x7a\x20\x4d\x32\x2e\x37\x39\x31\x2c\x31\x30\x2e\x36\x30\ -\x34\x63\x30\x2c\x31\x2e\x32\x33\x33\x2c\x31\x2c\x32\x2e\x32\x33\ -\x33\x2c\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\x33\x33\x48\x36\x2e\ -\x31\x34\x76\x2d\x32\x2e\x32\x33\x33\x6c\x33\x2e\x37\x36\x37\x2c\ -\x33\x2e\x36\x32\x38\x4c\x36\x2e\x31\x34\x2c\x31\x37\x2e\x38\x36\ -\x76\x2d\x32\x2e\x32\x33\x32\x48\x35\x2e\x30\x32\x33\x0d\x0a\x09\ -\x43\x32\x2e\x32\x34\x39\x2c\x31\x35\x2e\x36\x32\x38\x2c\x30\x2c\ -\x31\x33\x2e\x33\x37\x39\x2c\x30\x2c\x31\x30\x2e\x36\x30\x34\x56\ -\x38\x2e\x39\x33\x63\x30\x2d\x32\x2e\x37\x37\x34\x2c\x32\x2e\x32\ -\x34\x39\x2d\x35\x2e\x30\x32\x33\x2c\x35\x2e\x30\x32\x33\x2d\x35\ -\x2e\x30\x32\x33\x48\x36\x2e\x31\x34\x76\x32\x2e\x37\x39\x31\x48\ -\x35\x2e\x30\x32\x33\x63\x2d\x31\x2e\x32\x33\x33\x2c\x30\x2d\x32\ -\x2e\x32\x33\x33\x2c\x31\x2d\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\ -\x33\x32\x56\x31\x30\x2e\x36\x30\x34\x7a\x22\x2f\x3e\x0d\x0a\x3c\ -\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0a\ " qt_resource_name = b"\ \x00\x05\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ +\x00\x08\ +\x0a\xc3\x55\x87\ +\x00\x73\ +\x00\x74\x00\x65\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x09\ \x09\xba\x8f\xa7\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\ -\x00\x07\ -\x09\xc1\x5a\x27\ -\x00\x72\ -\x00\x75\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ -\x00\x08\ -\x0b\x63\x55\x87\ -\x00\x73\ -\x00\x74\x00\x6f\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0f\ \x0a\x15\x41\x07\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x11\ +\x0f\x59\x62\xa7\ +\x00\x72\ +\x00\x65\x00\x6c\x00\x6f\x00\x61\x00\x64\x00\x5f\x00\x73\x00\x63\x00\x72\x00\x69\x00\x70\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\ \x00\x08\ -\x0a\xc3\x55\x87\ +\x0b\x63\x55\x87\ \x00\x73\ -\x00\x74\x00\x65\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x74\x00\x6f\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x07\ +\x09\xc1\x5a\x27\ +\x00\x72\ +\x00\x75\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ " -qt_resource_struct = b"\ +qt_resource_struct_v1 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\ +\x00\x00\x00\x26\x00\x00\x00\x00\x00\x01\x00\x00\x03\x5c\ +\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x09\ +\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x13\x39\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xdd\ -\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x16\x2f\ -\x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x36\ -\x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x13\x49\ +\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x28\x23\ +\x00\x00\x00\x62\x00\x00\x00\x00\x00\x01\x00\x00\x17\x40\ " +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x26\x00\x00\x00\x00\x00\x01\x00\x00\x03\x5c\ +\x00\x00\x01\x62\x3c\x4f\x2a\xa0\ +\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x09\ +\x00\x00\x01\x62\x3c\x4f\x2a\xa0\ +\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x13\x39\ +\x00\x00\x01\x62\x3c\x4f\x2a\xa0\ +\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x62\x3c\x4f\x2a\xa0\ +\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x28\x23\ +\x00\x00\x01\x62\x3c\x4f\x2a\xa0\ +\x00\x00\x00\x62\x00\x00\x00\x00\x00\x01\x00\x00\x17\x40\ +\x00\x00\x01\x6b\xc7\xe9\xfc\xa8\ +" -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/plugins/python/scripter/scripter.qrc b/plugins/python/scripter/scripter.qrc index 2266176553..af00c5dfc3 100644 --- a/plugins/python/scripter/scripter.qrc +++ b/plugins/python/scripter/scripter.qrc @@ -1,9 +1,10 @@ icons/debug.svg icons/debug_arrow.svg icons/run.svg + icons/reload_script.svg icons/step.svg icons/stop.svg diff --git a/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py b/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py index 3b5b5938fb..5bf2eea4b9 100644 --- a/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py +++ b/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py @@ -1,55 +1,55 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt import krita class CloseAction(QAction): def __init__(self, scripter, parent=None): super(CloseAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.close) self.setText(i18n("Close")) self.setObjectName('close') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) @property def parent(self): - return 'File' + return 'File', def close(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) msgBox.setInformativeText(i18n("Do you want to save the current document?")) msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: if not self.scripter.uicontroller.invokeAction('save'): return self.scripter.uicontroller.closeScripter() diff --git a/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py b/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py index ed18515229..a40a328a74 100644 --- a/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py +++ b/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py @@ -1,47 +1,47 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction from PyQt5.QtGui import QIcon, QPixmap, QKeySequence from scripter import resources_rc from PyQt5.QtCore import Qt import krita class DebugAction(QAction): def __init__(self, scripter, parent=None): super(DebugAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.debug) self.setText(i18n("Debug")) self.setToolTip(i18n("Debug Ctrl+D")) self.setIcon(QIcon(':/icons/debug.svg')) self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_D)) @property def parent(self): - return 'toolBar' + return 'toolBar', def debug(self): if self.scripter.uicontroller.invokeAction('save'): self.scripter.uicontroller.setActiveWidget(i18n('Debugger')) self.scripter.debugcontroller.start(self.scripter.documentcontroller.activeDocument) widget = self.scripter.uicontroller.findTabWidget(i81n('Debugger')) widget.startDebugger() diff --git a/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py b/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py index d446281fad..d75d102826 100644 --- a/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py +++ b/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py @@ -1,57 +1,57 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt import krita class NewAction(QAction): def __init__(self, scripter, parent=None): super(NewAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.new) self.setText(i18n("New")) self.setObjectName('new') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_N)) @property def parent(self): - return 'File' + return 'File', def new(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) msgBox.setText(i18n("The document has been modified.")) msgBox.setInformativeText(i18n("Do you want to save your changes?")) msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: self.scripter.uicontroller.invokeAction('save') self.scripter.documentcontroller.clearActiveDocument() self.scripter.uicontroller.setStatusBar() self.scripter.uicontroller.clearEditor() diff --git a/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py b/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py index eb88385e9d..70dbd5a641 100644 --- a/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py +++ b/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py @@ -1,58 +1,58 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt import krita import os class OpenAction(QAction): def __init__(self, scripter, parent=None): super(OpenAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.open) self.setText(i18n("Open")) self.setObjectName('open') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O)) @property def parent(self): - return 'File' + return 'File', def open(self): dialog = QFileDialog(self.scripter.uicontroller.mainWidget) dialog.setNameFilter(i18n("Python Files (*.py)")) if dialog.exec_(): try: selectedFile = dialog.selectedFiles()[0] _, fileExtension = os.path.splitext(selectedFile) if fileExtension == '.py': document = self.scripter.documentcontroller.openDocument(selectedFile) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) except Exception: QMessageBox.information(self.scripter.uicontroller.mainWidget, i18n("Invalid File"), i18n("Open files with .py extension")) diff --git a/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py b/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py index ec142f21dc..72ac17ba96 100644 --- a/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py +++ b/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py @@ -1,63 +1,66 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QMessageBox -from PyQt5.QtGui import QKeySequence +from PyQt5.QtGui import QIcon, QKeySequence from PyQt5.QtCore import Qt import krita class ReloadAction(QAction): def __init__(self, scripter, parent=None): super(ReloadAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.reloadFile) self.setText(i18n("Reload File")) self.setObjectName('reloadfile') self.setShortcut(QKeySequence(Qt.ALT + Qt.Key_R)) + self.setToolTip(i18n('Reload File Alt+R')) + self.setIcon(QIcon(':/icons/reload_script.svg')) + @property def parent(self): - return 'File' + return 'File', 'toolBar' def reloadFile(self): # get the currently open document's path curr_doc_fpath = '' document = self.scripter.documentcontroller._activeDocument if document is None: QMessageBox.critical(self.scripter.uicontroller.mainWidget, i18n("No existing document"), i18n("Please specify a document by opening it before reloading")) return else: curr_doc_fpath = document.filePath # clear the editor self.scripter.documentcontroller.clearActiveDocument() self.scripter.uicontroller.setStatusBar() self.scripter.uicontroller.clearEditor() # reload the document document = self.scripter.documentcontroller.openDocument(curr_doc_fpath) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) return document diff --git a/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py b/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py index f35452f2b0..a834da3ccb 100644 --- a/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py +++ b/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py @@ -1,159 +1,159 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction from PyQt5.QtGui import QIcon, QKeySequence from PyQt5.QtCore import Qt import sys import traceback import inspect from . import docwrapper import krita if sys.version_info[0] > 2: import importlib from importlib.machinery import SourceFileLoader else: import imp PYTHON27 = sys.version_info.major == 2 and sys.version_info.minor == 7 PYTHON33 = sys.version_info.major == 3 and sys.version_info.minor == 3 PYTHON34 = sys.version_info.major == 3 and sys.version_info.minor == 4 EXEC_NAMESPACE = "__main__" # namespace that user scripts will run in class RunAction(QAction): def __init__(self, scripter, parent=None): super(RunAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.output = self.scripter.uicontroller.findTabWidget(i18n('Output'), 'OutPutTextEdit') self.triggered.connect(self.run) self.setText(i18n("Run")) self.setToolTip(i18n('Run Ctrl+R')) self.setIcon(QIcon(':/icons/run.svg')) self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) @property def parent(self): - return 'toolBar' + return 'toolBar', def run(self): """ This method execute python code from an activeDocument (file) or direct from editor (ui_scripter/editor/pythoneditor.py). When executing code from a file, we use importlib to load this module/file and with "users_script" name. That's implementation seeks for a "main()" function in the script. When executing code from editor without creating a file, we compile this script to bytecode and we execute this in an empty scope. That's faster than use exec directly and cleaner, because we are using an empty scope. """ self.scripter.uicontroller.setActiveWidget(i18n('Output')) stdout = sys.stdout stderr = sys.stderr output = docwrapper.DocWrapper(self.output.document()) if (self.editor._documentModified is True): output.write("==== Warning: Script not saved! ====\n") else: output.write("======================================\n") sys.stdout = output sys.stderr = output script = self.editor.document().toPlainText() document = self.scripter.documentcontroller.activeDocument try: if document and self.editor._documentModified is False: if PYTHON27: users_module = self.run_py2_document(document) else: users_module = self.run_py3_document(document) # maybe script is to be execed, maybe main needs to be invoked # if there is a main() then execute it, otherwise don't worry... if hasattr(users_module, "main") and inspect.isfunction(users_module.main): users_module.main() else: code = compile(script, '', 'exec') globals_dict = {"__name__": EXEC_NAMESPACE} exec(code, globals_dict) except SystemExit: # user typed quit() or exit() self.scripter.uicontroller.closeScripter() except Exception: # Provide context (line number and text) for an error that is caught. # Ordinarily, syntax and Indent errors are caught during initial # compilation in exec(), and the traceback traces back to this file. # So these need to be treated separately. # Other errors trace back to the file/script being run. type_, value_, traceback_ = sys.exc_info() if type_ == SyntaxError: errorMessage = "%s\n%s" % (value_.text.rstrip(), " " * (value_.offset - 1) + "^") # rstrip to remove trailing \n, output needs to be fixed width font for the ^ to align correctly errorText = "Syntax Error on line %s" % value_.lineno elif type_ == IndentationError: # (no offset is provided for an IndentationError errorMessage = value_.text.rstrip() errorText = "Unexpected Indent on line %s" % value_.lineno else: errorText = traceback.format_exception_only(type_, value_)[0] format_string = "In file: {0}\nIn function: {2} at line: {1}. Line with error:\n{3}" tbList = traceback.extract_tb(traceback_) tb = tbList[-1] errorMessage = format_string.format(*tb) m = "\n**********************\n%s\n%s\n**********************\n" % (errorText, errorMessage) output.write(m) sys.stdout = stdout sys.stderr = stderr # scroll to bottom of output bottom = self.output.verticalScrollBar().maximum() self.output.verticalScrollBar().setValue(bottom) def run_py2_document(self, document): """ Loads and executes an external script using Python 2 specific operations and returns the loaded module for further execution if needed. """ try: user_module = imp.load_source(EXEC_NAMESPACE, document.filePath) except Exception as e: raise e return user_module def run_py3_document(self, document): """ Loads and executes an external script using Python 3 specific operations and returns the loaded module for further execution if needed. """ spec = importlib.util.spec_from_file_location(EXEC_NAMESPACE, document.filePath) try: users_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(users_module) except AttributeError as e: # no module from spec if PYTHON34 or PYTHON33: loader = SourceFileLoader(EXEC_NAMESPACE, document.filePath) users_module = loader.load_module() else: raise e return users_module diff --git a/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py b/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py index 887395ba8b..9691666fa1 100644 --- a/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py +++ b/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py @@ -1,63 +1,63 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QFileDialog from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt import krita class SaveAction(QAction): def __init__(self, scripter, parent=None): super(SaveAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.save) self.setText(i18n("Save")) self.setObjectName('save') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) @property def parent(self): - return 'File' + return 'File', def save(self): text = self.editor.toPlainText() fileName = '' if not self.scripter.documentcontroller.activeDocument: fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, i18n("Save Python File"), '', i18n("Python File (*.py)"))[0] if not fileName: return # don't validate file name - trust user to specify the extension they want # getSaveFileName will add ".py" if there is no extension. # It will strip a trailing period and, in each case, test for file collisions document = self.scripter.documentcontroller.saveDocument(text, fileName) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') self.editor._documentModified = False self.scripter.uicontroller.setStatusModified() return document diff --git a/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py b/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py index 2687e44bfa..d4977ceed8 100644 --- a/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py +++ b/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py @@ -1,61 +1,61 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QFileDialog from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt import krita class SaveAsAction(QAction): def __init__(self, scripter, parent=None): super(SaveAsAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.save) self.setText(i18n("Save As")) self.setObjectName('saveas') self.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S)) @property def parent(self): - return 'File' + return 'File', def save(self): text = self.editor.toPlainText() fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, i18n("Save Python File"), '', i18n("Python File (*.py)"))[0] if not fileName: return # don't validate file name - trust user to specify the extension they want # getSaveFileName will add ".py" if there is no extension. # It will strip a trailing period and, in each case, test for file collisions document = self.scripter.documentcontroller.saveDocument(text, fileName, save_as=True) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') self.editor._documentModified = False self.scripter.uicontroller.setStatusModified() return document diff --git a/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py b/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py index c5e138e851..a40217d72f 100644 --- a/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py +++ b/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py @@ -1,50 +1,50 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction from PyQt5.QtCore import Qt from . import settingsdialog import krita class SettingsAction(QAction): def __init__(self, scripter, parent=None): super(SettingsAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.openSettings) self.settingsDialog = settingsdialog.SettingsDialog(self.scripter) self.settingsDialog.setWindowModality(Qt.WindowModal) self.settingsDialog.setFixedSize(400, 250) self.setText(i18n("Settings")) @property def parent(self): - return 'File' + return 'File', def openSettings(self): self.settingsDialog.show() self.settingsDialog.exec_() def readSettings(self): self.settingsDialog.readSettings(self.scripter.settings) def writeSettings(self): self.settingsDialog.writeSettings(self.scripter.settings) diff --git a/plugins/python/scripter/uicontroller.py b/plugins/python/scripter/uicontroller.py index 087d8b1408..3061ca4e46 100644 --- a/plugins/python/scripter/uicontroller.py +++ b/plugins/python/scripter/uicontroller.py @@ -1,265 +1,267 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtCore import Qt, QObject, QFileInfo, QRect from PyQt5.QtGui import QTextCursor, QPalette from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget, QLabel, QVBoxLayout, QMessageBox, QSplitter, QSizePolicy) from .ui_scripter.syntax import syntax, syntaxstyles from .ui_scripter.editor import pythoneditor from . import scripterdialog import importlib import krita KEY_GEOMETRY = "geometry" DEFAULT_GEOMETRY = QRect(600, 200, 400, 500) # essentially randomly placed class Elided_Text_Label(QLabel): mainText = str() def __init__(self, parent=None): super(QLabel, self).__init__(parent) self.setMinimumWidth(self.fontMetrics().width("...")) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) def setMainText(self, text=str()): self.mainText = text self.elideText() def elideText(self): self.setText(self.fontMetrics().elidedText(self.mainText, Qt.ElideRight, self.width())) def resizeEvent(self, event): self.elideText() class UIController(object): documentModifiedText = "" documentStatusBarText = "untitled" def __init__(self): self.mainWidget = scripterdialog.ScripterDialog(self) self.actionToolbar = QToolBar('toolBar', self.mainWidget) self.menu_bar = QMenuBar(self.mainWidget) self.actionToolbar.setObjectName('toolBar') self.menu_bar.setObjectName('menuBar') self.actions = [] self.mainWidget.setWindowModality(Qt.NonModal) def initialize(self, scripter): self.editor = pythoneditor.CodeEditor(scripter) self.tabWidget = QTabWidget() self.statusBar = Elided_Text_Label() self.statusBar.setMainText('untitled') self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle()) p = self.editor.palette() p.setColor(QPalette.Base, syntaxstyles.DefaultSyntaxStyle()['background'].foreground().color()) p.setColor(QPalette.Text, syntaxstyles.DefaultSyntaxStyle()['foreground'].foreground().color()) self.editor.setPalette(p) self.scripter = scripter self.loadMenus() self.loadWidgets() self.loadActions() self._readSettings() # sets window size vbox = QVBoxLayout(self.mainWidget) vbox.addWidget(self.menu_bar) vbox.addWidget(self.actionToolbar) self.splitter.addWidget(self.editor) self.splitter.addWidget(self.tabWidget) vbox.addWidget(self.splitter) vbox.addWidget(self.statusBar) self.mainWidget.setWindowTitle(i18n("Scripter")) self.mainWidget.setSizeGripEnabled(True) self.mainWidget.show() self.mainWidget.activateWindow() self.editor.undoAvailable.connect(self.setStatusModified) def loadMenus(self): self.addMenu(i18n('File'), i18n('File')) def addMenu(self, menuName, parentName): parent = self.menu_bar.findChild(QObject, parentName) self.newMenu = None if parent: self.newMenu = parent.addMenu(menuName) else: self.newMenu = self.menu_bar.addMenu(menuName) self.newMenu.setObjectName(menuName) return self.newMenu def loadActions(self): module_path = 'scripter.ui_scripter.actions' actions_module = importlib.import_module(module_path) modules = [] for class_path in actions_module.action_classes: _module = class_path[:class_path.rfind(".")] _klass = class_path[class_path.rfind(".") + 1:] modules.append(dict(module='{0}.{1}'.format(module_path, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) action_class = getattr(m, module['klass']) obj = action_class(self.scripter) - parent = self.mainWidget.findChild(QObject, i18n(obj.parent)) - self.actions.append(dict(action=obj, parent=parent)) + obj_parent = obj.parent + for name in obj_parent: + parent = self.mainWidget.findChild(QObject, i18n(name)) + self.actions.append(dict(action=obj, parent=parent)) for action in self.actions: action['parent'].addAction(action['action']) def loadWidgets(self): modulePath = 'scripter.ui_scripter.tabwidgets' widgetsModule = importlib.import_module(modulePath) modules = [] for class_path in widgetsModule.widgetClasses: _module = class_path[:class_path.rfind(".")] _klass = class_path[class_path.rfind(".") + 1:] modules.append(dict(module='{0}.{1}'.format(modulePath, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) widgetClass = getattr(m, module['klass']) obj = widgetClass(self.scripter) self.tabWidget.addTab(obj, obj.objectName()) def invokeAction(self, actionName): for action in self.actions: if action['action'].objectName() == actionName: method = getattr(action['action'], actionName) if method: return method() def findTabWidget(self, widgetName, childName=''): for index in range(self.tabWidget.count()): widget = self.tabWidget.widget(index) if widget.objectName() == widgetName: if childName: widget = widget.findChild(QObject, childName) return widget def showException(self, exception): QMessageBox.critical(self.editor, i18n("Error Running Script"), str(exception)) def setDocumentEditor(self, document): self.editor.clear() self.editor.moveCursor(QTextCursor.Start) self.editor._documentModified = False self.editor.setPlainText(document.data) self.editor.moveCursor(QTextCursor.End) def setStatusBar(self, value='untitled'): self.documentStatusBarText = value self.statusBar.setMainText(self.documentStatusBarText + self.documentModifiedText) def setStatusModified(self): self.documentModifiedText = "" if (self.editor._documentModified is True): self.documentModifiedText = " [Modified]" self.statusBar.setMainText(self.documentStatusBarText + self.documentModifiedText) def setActiveWidget(self, widgetName): widget = self.findTabWidget(widgetName) if widget: self.tabWidget.setCurrentWidget(widget) def setStepped(self, status): self.editor.setStepped(status) def clearEditor(self): self.editor.clear() def repaintDebugArea(self): self.editor.repaintDebugArea() def closeScripter(self): self.mainWidget.close() def _writeSettings(self): """ _writeSettings is a method invoked when the scripter starts, making control inversion. Actions can implement a writeSettings method to save your own settings without this method to know about it. """ self.scripter.settings.beginGroup('scripter') document = self.scripter.documentcontroller.activeDocument if document: self.scripter.settings.setValue('activeDocumentPath', document.filePath) else: self.scripter.settings.setValue('activeDocumentPath', '') self.scripter.settings.setValue('editorFontSize', self.editor.fontInfo().pointSize()) for action in self.actions: writeSettings = getattr(action['action'], "writeSettings", None) if callable(writeSettings): writeSettings() # Window Geometry rect = self.mainWidget.geometry() self.scripter.settings.setValue(KEY_GEOMETRY, rect) self.scripter.settings.endGroup() def _readSettings(self): """ It's similar to _writeSettings, but reading the settings when the ScripterDialog is closed. """ self.scripter.settings.beginGroup('scripter') activeDocumentPath = self.scripter.settings.value('activeDocumentPath', '') if activeDocumentPath: if QFileInfo(activeDocumentPath).exists(): document = self.scripter.documentcontroller.openDocument(activeDocumentPath) self.setStatusBar(document.filePath) self.setDocumentEditor(document) for action in self.actions: readSettings = getattr(action['action'], "readSettings", None) if callable(readSettings): readSettings() pointSize = self.scripter.settings.value('editorFontSize', str(self.editor.fontInfo().pointSize())) self.editor.setFontSize(int(pointSize)) # Window Geometry rect = self.scripter.settings.value(KEY_GEOMETRY, DEFAULT_GEOMETRY) self.mainWidget.setGeometry(rect) self.scripter.settings.endGroup() def _saveSettings(self): self.scripter.settings.sync() diff --git a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop index 13e3f558cb..0550f859b4 100644 --- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop @@ -1,51 +1,51 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenscripts X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Scripts Name[ar]=عشرُ سكربتات Name[ca]=Deu scripts Name[ca@valencia]=Deu scripts Name[en_GB]=Ten Scripts Name[es]=Diez guiones Name[eu]=Hamar script Name[fi]=Kymmenen skriptiä Name[fr]=Raccourcis des scripts Name[gl]=Dez scripts Name[is]=Tíu skriftur Name[it]=Dieci script Name[ko]=10개의 스크립트 Name[nl]=Tien scripts Name[nn]=Ti skript Name[pl]=Skrypty Ten Name[pt]=Dez Programas Name[sv]=Tio skript Name[tr]=On Betik Name[uk]=Десять скриптів Name[x-test]=xxTen Scriptsxx Name[zh_CN]=十大脚本 Name[zh_TW]=10 個指令稿 Comment=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[ar]=ملحقة بِ‍«پيثون» لإنشاء ١٠ إجراءات وإسنادها إلى سكربتات «پيثون» Comment[ca]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[ca@valencia]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python Comment[eu]=Hamar ekintza sortu eta haiek Python-scriptei esleitzeko Python-oinarridun plugin bat Comment[fi]=Python-pohjainen liitännäinen kymmenen toiminnon luomiseksi kytkemiseksi Python-skripteihin Comment[fr]=Module externe basé sur Python pour créer dix actions et les assigner à des scripts Python Comment[gl]=Un complemento escrito en Python para crear dez accións e asignalas a scripts escritos en Python. Comment[it]=Un'estensione basata su Python per creare dieci azioni e assegnarle a script Python Comment[ko]=10개의 작업을 생성하고 이를 Python 스크립트에 할당하는 Python 기반 플러그인 Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts -Comment[nn]=Python-basert programtillegg for å leggja til ti handlingar og tildela dei til Python-skript +Comment[nn]=Python-basert tillegg for å leggja til ti handlingar og tildela dei til Python-skript Comment[pl]=Wtyczka oparta na Pythonie do tworzenia działań i przypisywanie ich skryptom Pythona Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript Comment[tr]=On eylem oluşturmak ve Python betiklerine atamak için Python tabanlı bir eklenti Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx Comment[zh_CN]=基于 Python 的用于创建十个操作并将它们指定到特定 Python 脚本的插件 Comment[zh_TW]=基於 Python 的外掛程式,用於建立 10 個動作並且將它們指定給 Python 指令稿 diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc index 0b8c97c8d9..ad6a3173a8 100644 --- a/plugins/tools/basictools/kis_tool_line.cc +++ b/plugins/tools/basictools/kis_tool_line.cc @@ -1,368 +1,369 @@ /* * kis_tool_line.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2007,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_tool_line.h" #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_canvas2.h" #include "kis_painting_information_builder.h" #include "kis_tool_line_helper.h" const KisCoordinatesConverter* getCoordinatesConverter(KoCanvasBase * canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); return kritaCanvas->coordinatesConverter(); } KisToolLine::KisToolLine(KoCanvasBase * canvas) : KisToolShape(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)), m_showGuideline(true), m_strokeIsRunning(false), m_infoBuilder(new KisConverterPaintingInformationBuilder(getCoordinatesConverter(canvas))), m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))), m_strokeUpdateCompressor(500, KisSignalCompressor::POSTPONE), m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_line"); setSupportOutline(true); connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisToolLine::~KisToolLine() { } void KisToolLine::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolLine::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolLine::deactivate() { KisToolPaint::deactivate(); cancelStroke(); } QWidget* KisToolLine::createOptionWidget() { QWidget* widget = KisToolPaint::createOptionWidget(); m_chkUseSensors = new QCheckBox(i18n("Use sensors")); addOptionWidgetOption(m_chkUseSensors); m_chkShowPreview = new QCheckBox(i18n("Show Preview")); addOptionWidgetOption(m_chkShowPreview); m_chkShowGuideline = new QCheckBox(i18n("Show Guideline")); addOptionWidgetOption(m_chkShowGuideline); // hook up connections for value changing connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) ); connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) ); connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) ); // read values in from configuration m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true)); m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true)); m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true)); return widget; } void KisToolLine::setUseSensors(bool value) { configGroup.writeEntry("useSensors", value); } void KisToolLine::setShowGuideline(bool value) { m_showGuideline = value; configGroup.writeEntry("showGuideline", value); } void KisToolLine::setShowPreview(bool value) { configGroup.writeEntry("showPreview", value); } void KisToolLine::requestStrokeCancellation() { cancelStroke(); } void KisToolLine::requestStrokeEnd() { // Terminate any in-progress strokes if (nodePaintAbility() == PAINT && m_helper->isRunning()) { endStroke(); } } void KisToolLine::updatePreviewTimer(bool showGuideline) { // If the user disables the guideline, we will want to try to draw some // preview lines even if they're slow, so set the timer to FIRST_ACTIVE. if (showGuideline) { m_strokeUpdateCompressor.setMode(KisSignalCompressor::POSTPONE); } else { m_strokeUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } } void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if(mode() == KisTool::PAINT_MODE) { paintLine(gc,QRect()); } KisToolPaint::paint(gc,converter); } void KisToolLine::beginPrimaryAction(KoPointerEvent *event) { NodePaintAbility nodeAbility = nodePaintAbility(); if (nodeAbility == UNPAINTABLE || !nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); // Always show guideline on vector layers m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT; updatePreviewTimer(m_showGuideline); m_helper->setEnabled((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape); m_helper->setUseSensors(m_chkUseSensors->isChecked()); m_helper->start(event, canvas()->resourceManager()); m_startPoint = convertToPixelCoordAndSnap(event); m_endPoint = m_startPoint; m_lastUpdatedPoint = m_startPoint; m_strokeIsRunning = true; } void KisToolLine::updateStroke() { if (!m_strokeIsRunning) return; m_helper->repaintLine(canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolLine::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeIsRunning) return; // First ensure the old guideline is deleted updateGuideline(); QPointF pos = convertToPixelCoordAndSnap(event); if (event->modifiers() == Qt::AltModifier) { QPointF trans = pos - m_endPoint; m_helper->translatePoints(trans); m_startPoint += trans; m_endPoint += trans; } else if (event->modifiers() == Qt::ShiftModifier) { pos = straightLine(pos); m_helper->addPoint(event, pos); } else { m_helper->addPoint(event, pos); } m_endPoint = pos; // Draw preview if requested if (m_chkShowPreview->isChecked()) { // If the cursor has moved a significant amount, immediately clear the // current preview and redraw. Otherwise, do slow redraws periodically. auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength(); if (updateDistance > 10) { m_helper->clearPaint(); m_longStrokeUpdateCompressor.stop(); m_strokeUpdateCompressor.start(); m_lastUpdatedPoint = pos; } else if (updateDistance > 1) { m_longStrokeUpdateCompressor.start(); } } updateGuideline(); KisToolPaint::requestUpdateOutline(event->point, event); } void KisToolLine::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); updateGuideline(); endStroke(); } void KisToolLine::endStroke() { NodePaintAbility nodeAbility = nodePaintAbility(); if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == UNPAINTABLE) { + m_helper->clearPoints(); return; } const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if ((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape) { updateStroke(); m_helper->end(); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_startPoint)); path->lineTo(resolutionMatrix.map(m_endPoint)); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0); canvas()->addCommand(cmd); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } void KisToolLine::cancelStroke() { if (!m_strokeIsRunning) return; if (m_startPoint == m_endPoint) return; /** * The actual stroke is run by the timer so it is a legal * situation when m_strokeIsRunning is true, but the actual redraw * stroke is not running. */ if (m_helper->isRunning()) { m_helper->cancel(); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } QPointF KisToolLine::straightLine(QPointF point) { const QPointF lineVector = point - m_startPoint; qreal lineAngle = std::atan2(lineVector.y(), lineVector.x()); if (lineAngle < 0) { lineAngle += 2 * M_PI; } const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24; const quint32 constrainedLineIndex = static_cast((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5); const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES; const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y())); const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle)); const QPointF result = m_startPoint + constrainedLineVector; return result; } void KisToolLine::updateGuideline() { if (canvas()) { QRectF bound(m_startPoint, m_endPoint); canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3))); } } void KisToolLine::paintLine(QPainter& gc, const QRect&) { QPointF viewStartPos = pixelToView(m_startPoint); QPointF viewStartEnd = pixelToView(m_endPoint); if (m_showGuideline && canvas()) { QPainterPath path; path.moveTo(viewStartPos); path.lineTo(viewStartEnd); paintToolOutline(&gc, path); } } QString KisToolLine::quickHelp() const { return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines"); } diff --git a/plugins/tools/basictools/kis_tool_line_helper.cpp b/plugins/tools/basictools/kis_tool_line_helper.cpp index 4e1726a3da..28a268a608 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.cpp +++ b/plugins/tools/basictools/kis_tool_line_helper.cpp @@ -1,184 +1,190 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_line_helper.h" #include "kis_algebra_2d.h" #include "kis_painting_information_builder.h" #include "kis_image.h" struct KisToolLineHelper::Private { Private(KisPaintingInformationBuilder *_infoBuilder) : infoBuilder(_infoBuilder), useSensors(true), enabled(true) { } QVector linePoints; KisPaintingInformationBuilder *infoBuilder; bool useSensors; bool enabled; }; KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText) : KisToolFreehandHelper(infoBuilder, transactionText, new KisSmoothingOptions(false)), m_d(new Private(infoBuilder)) { } KisToolLineHelper::~KisToolLineHelper() { delete m_d; } void KisToolLineHelper::setEnabled(bool value) { m_d->enabled = value; } void KisToolLineHelper::setUseSensors(bool value) { m_d->useSensors = value; } void KisToolLineHelper::repaintLine(KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade) { if (!m_d->enabled) return; cancelPaint(); if (m_d->linePoints.isEmpty()) return; qreal startAngle = 0.0; if (m_d->linePoints.length() > 1) { startAngle = KisAlgebra2D::directionBetweenPoints(m_d->linePoints[0].pos(), m_d->linePoints[1].pos(), 0.0); } QVector::const_iterator it = m_d->linePoints.constBegin(); QVector::const_iterator end = m_d->linePoints.constEnd(); initPaintImpl(startAngle, *it, resourceManager, image, node, strokesFacade); ++it; while (it != end) { paintLine(*(it - 1), *it); ++it; } } void KisToolLineHelper::start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->startStroke(event, 0, resourceManager); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } m_d->linePoints.append(pi); } void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->continueStroke(event, 0); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } if (!overridePos.isNull()) { pi.setPos(overridePos); } if (m_d->linePoints.size() > 1) { const QPointF startPos = m_d->linePoints.first().pos(); const QPointF endPos = pi.pos(); const qreal maxDistance = kisDistance(startPos, endPos); const QPointF unit = (endPos - startPos) / maxDistance; QVector::iterator it = m_d->linePoints.begin(); ++it; while (it != m_d->linePoints.end()) { qreal dist = kisDistance(startPos, it->pos()); if (dist < maxDistance) { QPointF pos = startPos + unit * dist; it->setPos(pos); ++it; } else { it = m_d->linePoints.erase(it); } } } m_d->linePoints.append(pi); } void KisToolLineHelper::translatePoints(const QPointF &offset) { if (!m_d->enabled) return; QVector::iterator it = m_d->linePoints.begin(); while (it != m_d->linePoints.end()) { it->setPos(it->pos() + offset); ++it; } } void KisToolLineHelper::end() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); endPaint(); - m_d->linePoints.clear(); + clearPoints(); } void KisToolLineHelper::cancel() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); cancelPaint(); + clearPoints(); +} + + +void KisToolLineHelper::clearPoints() +{ m_d->linePoints.clear(); } void KisToolLineHelper::clearPaint() { if (!m_d->enabled) return; cancelPaint(); } diff --git a/plugins/tools/basictools/kis_tool_line_helper.h b/plugins/tools/basictools/kis_tool_line_helper.h index 8835c6a02a..13e3863e2c 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.h +++ b/plugins/tools/basictools/kis_tool_line_helper.h @@ -1,55 +1,56 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_LINE_HELPER_H #define __KIS_TOOL_LINE_HELPER_H #include "kis_tool_freehand_helper.h" class KisToolLineHelper : private KisToolFreehandHelper { public: KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText); ~KisToolLineHelper() override; void setEnabled(bool value); void setUseSensors(bool value); void repaintLine(KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade); void start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager); void addPoint(KoPointerEvent *event, const QPointF &overridePos = QPointF()); void translatePoints(const QPointF &offset); void end(); void cancel(); + void clearPoints(); void clearPaint(); using KisToolFreehandHelper::isRunning; private: struct Private; Private * const m_d; }; #endif /* __KIS_TOOL_LINE_HELPER_H */ diff --git a/plugins/tools/basictools/kis_tool_path.cc b/plugins/tools/basictools/kis_tool_path.cc index 914f3d5a1f..06e30ae19b 100644 --- a/plugins/tools/basictools/kis_tool_path.cc +++ b/plugins/tools/basictools/kis_tool_path.cc @@ -1,115 +1,116 @@ /* * Copyright (c) 2007 Sven Langkamp * 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_tool_path.h" #include #include #include KisToolPath::KisToolPath(KoCanvasBase * canvas) : DelegatedPathTool(canvas, Qt::ArrowCursor, new __KisToolPathLocalTool(canvas, this)) { } void KisToolPath::resetCursorStyle() { DelegatedPathTool::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolPath::requestStrokeEnd() { localTool()->endPathWithoutLastPoint(); } void KisToolPath::requestStrokeCancellation() { localTool()->cancelPath(); } void KisToolPath::mousePressEvent(KoPointerEvent *event) { if (!nodeEditable()) return; DelegatedPathTool::mousePressEvent(event); } // Install an event filter to catch right-click events. // The simplest way to accommodate the popup palette binding. // This code is duplicated in kis_tool_select_path.cc bool KisToolPath::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj); if (event->type() == QEvent::MouseButtonPress || - event->type() == QEvent::MouseButtonDblClick) { + event->type() == QEvent::MouseButtonDblClick) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton) { localTool()->removeLastPoint(); return true; } } else if (event->type() == QEvent::TabletPress) { QTabletEvent *tabletEvent = static_cast(event); if (tabletEvent->button() == Qt::RightButton) { localTool()->removeLastPoint(); return true; } } return false; } void KisToolPath::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { - Q_UNUSED(action) - mousePressEvent(event); + Q_UNUSED(action) + mousePressEvent(event); } void KisToolPath::continueAlternateAction(KoPointerEvent *event, AlternateAction action){ - Q_UNUSED(action) - mouseMoveEvent(event); + Q_UNUSED(action) + mouseMoveEvent(event); } void KisToolPath::endAlternateAction(KoPointerEvent *event, AlternateAction action) { - Q_UNUSED(action) - mouseReleaseEvent(event); + Q_UNUSED(action) + mouseReleaseEvent(event); } QList > KisToolPath::createOptionWidgets() { QList > widgets = DelegatedPathTool::createOptionWidgets(); return widgets; } __KisToolPathLocalTool::__KisToolPathLocalTool(KoCanvasBase * canvas, KisToolPath* parentTool) - : KoCreatePathTool(canvas), m_parentTool(parentTool) {} + : KoCreatePathTool(canvas) + , m_parentTool(parentTool) {} void __KisToolPathLocalTool::paintPath(KoPathShape &pathShape, QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); QTransform matrix; matrix.scale(m_parentTool->image()->xRes(), m_parentTool->image()->yRes()); matrix.translate(pathShape.position().x(), pathShape.position().y()); m_parentTool->paintToolOutline(&painter, m_parentTool->pixelToView(matrix.map(pathShape.outline()))); } void __KisToolPathLocalTool::addPathShape(KoPathShape* pathShape) { if (!KoCreatePathTool::tryMergeInPathShape(pathShape)) { m_parentTool->addPathShape(pathShape, kundo2_i18n("Draw Bezier Curve")); } } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp index 2a461cbdc2..0c7d85d5dd 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp @@ -1,433 +1,433 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonCalligraphicShape.h" #include #include #include "KarbonSimplifyPath.h" #include #include #include #include #include #include #undef M_PI const qreal M_PI = 3.1415927; KarbonCalligraphicShape::KarbonCalligraphicShape(qreal caps) : m_lastWasFlip(false) , m_caps(caps) { setShapeId(KoPathShapeId); setFillRule(Qt::WindingFill); setBackground(QSharedPointer(new KoColorBackground(QColor(Qt::black)))); setStroke(KoShapeStrokeModelSP()); } KarbonCalligraphicShape::KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs) - : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), + : KoParameterShape(rhs), m_points(rhs.m_points), m_lastWasFlip(rhs.m_lastWasFlip), m_caps(rhs.m_caps) { } KarbonCalligraphicShape::~KarbonCalligraphicShape() { } KoShape *KarbonCalligraphicShape::cloneShape() const { return new KarbonCalligraphicShape(*this); } void KarbonCalligraphicShape::appendPoint(const QPointF &point, qreal angle, qreal width) { // convert the point from canvas to shape coordinates QPointF p = point - position(); KarbonCalligraphicPoint *calligraphicPoint = new KarbonCalligraphicPoint(p, angle, width); QList handles = this->handles(); handles.append(p); setHandles(handles); m_points.append(calligraphicPoint); appendPointToPath(*calligraphicPoint); // make the angle of the first point more in line with the actual // direction if (m_points.count() == 4) { m_points[0]->setAngle(angle); m_points[1]->setAngle(angle); m_points[2]->setAngle(angle); } } void KarbonCalligraphicShape::appendPointToPath(const KarbonCalligraphicPoint &p) { qreal dx = std::cos(p.angle()) * p.width(); qreal dy = std::sin(p.angle()) * p.width(); // find the outline points QPointF p1 = p.point() - QPointF(dx / 2, dy / 2); QPointF p2 = p.point() + QPointF(dx / 2, dy / 2); if (pointCount() == 0) { moveTo(p1); lineTo(p2); normalize(); return; } // pointCount > 0 bool flip = (pointCount() >= 2) ? flipDetected(p1, p2) : false; // if there was a flip add additional points if (flip) { appendPointsToPathAux(p2, p1); if (pointCount() > 4) { smoothLastPoints(); } } appendPointsToPathAux(p1, p2); if (pointCount() > 4) { smoothLastPoints(); if (flip) { int index = pointCount() / 2; // find the last two points KoPathPoint *last1 = pointByIndex(KoPathPointIndex(0, index - 1)); KoPathPoint *last2 = pointByIndex(KoPathPointIndex(0, index)); last1->removeControlPoint1(); last1->removeControlPoint2(); last2->removeControlPoint1(); last2->removeControlPoint2(); m_lastWasFlip = true; } if (m_lastWasFlip) { int index = pointCount() / 2; // find the previous two points KoPathPoint *prev1 = pointByIndex(KoPathPointIndex(0, index - 2)); KoPathPoint *prev2 = pointByIndex(KoPathPointIndex(0, index + 1)); prev1->removeControlPoint1(); prev1->removeControlPoint2(); prev2->removeControlPoint1(); prev2->removeControlPoint2(); if (!flip) { m_lastWasFlip = false; } } } normalize(); // add initial cap if it's the fourth added point // this code is here because this function is called from different places // pointCount() == 8 may causes crashes because it doesn't take possible // flips into account if (m_points.count() >= 4 && &p == m_points[3]) { addCap(3, 0, 0, true); // duplicate the last point to make the points remain "balanced" // needed to keep all indexes code (else I would need to change // everything in the code...) KoPathPoint *last = pointByIndex(KoPathPointIndex(0, pointCount() - 1)); KoPathPoint *newPoint = new KoPathPoint(this, last->point()); insertPoint(newPoint, KoPathPointIndex(0, pointCount())); close(); } } void KarbonCalligraphicShape::appendPointsToPathAux(const QPointF &p1, const QPointF &p2) { KoPathPoint *pathPoint1 = new KoPathPoint(this, p1); KoPathPoint *pathPoint2 = new KoPathPoint(this, p2); // calculate the index of the insertion position int index = pointCount() / 2; insertPoint(pathPoint2, KoPathPointIndex(0, index)); insertPoint(pathPoint1, KoPathPointIndex(0, index)); } void KarbonCalligraphicShape::smoothLastPoints() { int index = pointCount() / 2; smoothPoint(index - 2); smoothPoint(index + 1); } void KarbonCalligraphicShape::smoothPoint(const int index) { if (pointCount() < index + 2) { return; } else if (index < 1) { return; } const KoPathPointIndex PREV(0, index - 1); const KoPathPointIndex INDEX(0, index); const KoPathPointIndex NEXT(0, index + 1); QPointF prev = pointByIndex(PREV)->point(); QPointF point = pointByIndex(INDEX)->point(); QPointF next = pointByIndex(NEXT)->point(); QPointF vector = next - prev; qreal dist = (QLineF(prev, next)).length(); // normalize the vector (make it's size equal to 1) if (!qFuzzyCompare(dist + 1, 1)) { vector /= dist; } qreal mult = 0.35; // found by trial and error, might not be perfect... // distance of the control points from the point qreal dist1 = (QLineF(point, prev)).length() * mult; qreal dist2 = (QLineF(point, next)).length() * mult; QPointF vector1 = vector * dist1; QPointF vector2 = vector * dist2; QPointF controlPoint1 = point - vector1; QPointF controlPoint2 = point + vector2; pointByIndex(INDEX)->setControlPoint1(controlPoint1); pointByIndex(INDEX)->setControlPoint2(controlPoint2); } const QRectF KarbonCalligraphicShape::lastPieceBoundingRect() { if (pointCount() < 6) { return QRectF(); } int index = pointCount() / 2; QPointF p1 = pointByIndex(KoPathPointIndex(0, index - 3))->point(); QPointF p2 = pointByIndex(KoPathPointIndex(0, index - 2))->point(); QPointF p3 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF p4 = pointByIndex(KoPathPointIndex(0, index))->point(); QPointF p5 = pointByIndex(KoPathPointIndex(0, index + 1))->point(); QPointF p6 = pointByIndex(KoPathPointIndex(0, index + 2))->point(); // TODO: also take the control points into account QPainterPath p; p.moveTo(p1); p.lineTo(p2); p.lineTo(p3); p.lineTo(p4); p.lineTo(p5); p.lineTo(p6); return p.boundingRect().translated(position()); } bool KarbonCalligraphicShape::flipDetected(const QPointF &p1, const QPointF &p2) { // detect the flip caused by the angle changing 180 degrees // thus detect the boundary crossing int index = pointCount() / 2; QPointF last1 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF last2 = pointByIndex(KoPathPointIndex(0, index))->point(); int sum1 = std::abs(ccw(p1, p2, last1) + ccw(p1, last2, last1)); int sum2 = std::abs(ccw(p2, p1, last2) + ccw(p2, last1, last2)); // if there was a flip return sum1 < 2 && sum2 < 2; } int KarbonCalligraphicShape::ccw(const QPointF &p1, const QPointF &p2,const QPointF &p3) { // calculate two times the area of the triangle formed by the points given qreal area2 = (p2.x() - p1.x()) * (p3.y() - p1.y()) - (p2.y() - p1.y()) * (p3.x() - p1.x()); if (area2 > 0) { return +1; // the points are given in counterclockwise order } else if (area2 < 0) { return -1; // the points are given in clockwise order } else { return 0; // the points form a degenerate triangle } } void KarbonCalligraphicShape::setSize(const QSizeF &newSize) { // QSizeF oldSize = size(); // TODO: check KoParameterShape::setSize(newSize); } QPointF KarbonCalligraphicShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int i = 0; i < m_points.size(); ++i) { m_points[i]->setPoint(matrix.map(m_points[i]->point())); } return offset; } void KarbonCalligraphicShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); m_points[handleId]->setPoint(point); } void KarbonCalligraphicShape::updatePath(const QSizeF &size) { Q_UNUSED(size); QPointF pos = position(); // remove all points clear(); setPosition(QPoint(0, 0)); Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { appendPointToPath(*p); } simplifyPath(); QList handles; Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { handles.append(p->point()); } setHandles(handles); setPosition(pos); } void KarbonCalligraphicShape::simplifyPath() { if (m_points.count() < 2) { return; } close(); // add final cap addCap(m_points.count() - 2, m_points.count() - 1, pointCount() / 2); // TODO: the error should be proportional to the width // and it shouldn't be a magic number karbonSimplifyPath(this, 0.3); } void KarbonCalligraphicShape::addCap(int index1, int index2, int pointIndex, bool inverted) { QPointF p1 = m_points[index1]->point(); QPointF p2 = m_points[index2]->point(); // TODO: review why spikes can appear with a lower limit QPointF delta = p2 - p1; if (delta.manhattanLength() < 1.0) { return; } QPointF direction = QLineF(QPointF(0, 0), delta).unitVector().p2(); qreal width = m_points[index2]->width(); QPointF p = p2 + direction * m_caps * width; KoPathPoint *newPoint = new KoPathPoint(this, p); qreal angle = m_points[index2]->angle(); if (inverted) { angle += M_PI; } qreal dx = std::cos(angle) * width; qreal dy = std::sin(angle) * width; newPoint->setControlPoint1(QPointF(p.x() - dx / 2, p.y() - dy / 2)); newPoint->setControlPoint2(QPointF(p.x() + dx / 2, p.y() + dy / 2)); insertPoint(newPoint, KoPathPointIndex(0, pointIndex)); } QString KarbonCalligraphicShape::pathShapeId() const { return KarbonCalligraphicShapeId; } void KarbonCalligraphicShape::simplifyGuidePath() { // do not attempt to simplify if there are too few points if (m_points.count() < 3) { return; } QList points; Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { points.append(p->point()); } // cumulative data used to determine if the point can be removed qreal widthChange = 0; qreal directionChange = 0; QList::iterator i = m_points.begin() + 2; while (i != m_points.end() - 1) { QPointF point = (*i)->point(); qreal width = (*i)->width(); qreal prevWidth = (*(i - 1))->width(); qreal widthDiff = width - prevWidth; widthDiff /= qMax(width, prevWidth); qreal directionDiff = 0; if ((i + 1) != m_points.end()) { QPointF prev = (*(i - 1))->point(); QPointF next = (*(i + 1))->point(); directionDiff = QLineF(prev, point).angleTo(QLineF(point, next)); if (directionDiff > 180) { directionDiff -= 360; } } if (directionChange * directionDiff >= 0 && qAbs(directionChange + directionDiff) < 20 && widthChange * widthDiff >= 0 && qAbs(widthChange + widthDiff) < 0.1) { // deleted point delete *i; i = m_points.erase(i); directionChange += directionDiff; widthChange += widthDiff; } else { // keep point directionChange = 0; widthChange = 0; ++i; } } updatePath(QSizeF()); } diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.cc b/plugins/tools/selectiontools/kis_tool_select_outline.cc index 43495b988d..667b425177 100644 --- a/plugins/tools/selectiontools/kis_tool_select_outline.cc +++ b/plugins/tools/selectiontools/kis_tool_select_outline.cc @@ -1,283 +1,278 @@ /* * kis_tool_select_freehand.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_select_outline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include "canvas/kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_algebra_2d.h" #define FEEDBACK_LINE_WIDTH 2 KisToolSelectOutline::KisToolSelectOutline(KoCanvasBase * canvas) : KisToolSelect(canvas, KisCursor::load("tool_outline_selection_cursor.png", 5, 5), i18n("Outline Selection")), m_continuedMode(false) { } KisToolSelectOutline::~KisToolSelectOutline() { } void KisToolSelectOutline::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { m_continuedMode = true; } - KisToolSelect::keyPressEvent(event); } void KisToolSelectOutline::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control || !(event->modifiers() & Qt::ControlModifier)) { m_continuedMode = false; if (mode() != PAINT_MODE && !m_points.isEmpty()) { finishSelectionAction(); } } KisToolSelect::keyReleaseEvent(event); } void KisToolSelectOutline::mouseMoveEvent(KoPointerEvent *event) { - KisToolSelect::mouseMoveEvent(event); if (selectionDragInProgress()) return; m_lastCursorPos = convertToPixelCoord(event); if (m_continuedMode && mode() != PAINT_MODE) { updateContinuedMode(); } } void KisToolSelectOutline::beginPrimaryAction(KoPointerEvent *event) { - KisToolSelectBase::beginPrimaryAction(event); if (selectionDragInProgress()) return; if (!selectionEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); if (m_continuedMode && !m_points.isEmpty()) { m_paintPath.lineTo(pixelToView(convertToPixelCoord(event))); } else { m_paintPath.moveTo(pixelToView(convertToPixelCoord(event))); } m_points.append(convertToPixelCoord(event)); } void KisToolSelectOutline::continuePrimaryAction(KoPointerEvent *event) { - KisToolSelectBase::continuePrimaryAction(event); if (selectionDragInProgress()) return; CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPointF point = convertToPixelCoord(event); m_paintPath.lineTo(pixelToView(point)); m_points.append(point); updateFeedback(); - - } void KisToolSelectOutline::endPrimaryAction(KoPointerEvent *event) { + Q_UNUSED(event); const bool hadMoveInProgress = selectionDragInProgress(); - KisToolSelectBase::endPrimaryAction(event); + if (hadMoveInProgress) return; CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_continuedMode) { finishSelectionAction(); m_points.clear(); // ensure points are always cleared } } void KisToolSelectOutline::finishSelectionAction() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); const QRectF boundingRect = KisAlgebra2D::accumulateBounds(m_points); const QRectF boundingViewRect = pixelToView(boundingRect); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline")); if (helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) { return; } if (m_points.count() > 2) { QApplication::setOverrideCursor(KisCursor::waitCursor()); const SelectionMode mode = helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(), selectionMode(), selectionAction()); if (mode == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPolygon(m_points); QPainterPath cache; cache.addPolygon(m_points); cache.closeSubpath(); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_points[0])); for (int i = 1; i < m_points.count(); i++) path->lineTo(resolutionMatrix.map(m_points[i])); path->close(); path->normalize(); helper.addSelectionShape(path, selectionAction()); } QApplication::restoreOverrideCursor(); } m_points.clear(); m_paintPath = QPainterPath(); } void KisToolSelectOutline::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if ((mode() == KisTool::PAINT_MODE || m_continuedMode) && !m_points.isEmpty()) { QPainterPath outline = m_paintPath; if (m_continuedMode && mode() != KisTool::PAINT_MODE) { outline.lineTo(pixelToView(m_lastCursorPos)); } paintToolOutline(&gc, outline); } } void KisToolSelectOutline::updateFeedback() { if (m_points.count() > 1) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectOutline::updateContinuedMode() { if (!m_points.isEmpty()) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectOutline::deactivate() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); m_continuedMode = false; KisTool::deactivate(); } void KisToolSelectOutline::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_outline_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_outline_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/svgtexttool/SvgTextTool.cpp b/plugins/tools/svgtexttool/SvgTextTool.cpp index 4548adca0d..b735ea7457 100644 --- a/plugins/tools/svgtexttool/SvgTextTool.cpp +++ b/plugins/tools/svgtexttool/SvgTextTool.cpp @@ -1,430 +1,435 @@ /* This file is part of the KDE project Copyright 2017 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SvgTextTool.h" #include "KoSvgTextShape.h" #include "SvgTextChangeCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoToolManager.h" #include "KoCanvasResourceProvider.h" #include "SvgTextEditor.h" #include "KisHandlePainterHelper.h" #include SvgTextTool::SvgTextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_editor(0) , m_dragStart( 0, 0) , m_dragEnd( 0, 0) , m_dragging(false) { } SvgTextTool::~SvgTextTool() { if(m_editor) { m_editor->close(); } } void SvgTextTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); useCursor(Qt::ArrowCursor); if (shapes.size() == 1) { KoSvgTextShape *textShape = dynamic_cast(*shapes.constBegin()); if (!textShape) { koSelection()->deselectAll(); } else { // if we are a text shape...and the proxy tells us we want to edit the shape. open the text editor if (canvas()->selectedShapesProxy()->isRequestingToBeEdited()) { showEditor(); } } } else if (shapes.size() > 1) { KoSvgTextShape *foundTextShape = 0; Q_FOREACH (KoShape *shape, shapes) { KoSvgTextShape *textShape = dynamic_cast(shape); if (textShape) { foundTextShape = textShape; break; } } koSelection()->deselectAll(); if (foundTextShape) { koSelection()->select(foundTextShape); } } } void SvgTextTool::deactivate() { KoToolBase::deactivate(); QRectF updateRect = m_hoveredShapeHighlightRect; KoSvgTextShape *shape = selectedShape(); if (shape) { updateRect |= shape->boundingRect(); } m_hoveredShapeHighlightRect = QRectF(); canvas()->updateCanvas(updateRect); } QWidget *SvgTextTool::createOptionWidget() { QWidget *optionWidget = new QWidget(); QGridLayout *layout = new QGridLayout(optionWidget); m_configGroup = KSharedConfig::openConfig()->group(toolId()); QGroupBox *defsOptions = new QGroupBox(i18n("Create new texts with...")); QVBoxLayout *defOptionsLayout = new QVBoxLayout(); defsOptions->setLayout(defOptionsLayout); m_defFont = new QFontComboBox(); QString storedFont = m_configGroup.readEntry("defaultFont", QApplication::font().family()); m_defFont->setCurrentFont(QFont(storedFont)); defsOptions->layout()->addWidget(m_defFont); m_defPointSize = new QComboBox(); Q_FOREACH (int size, QFontDatabase::standardSizes()) { m_defPointSize->addItem(QString::number(size)+" pt"); } int storedSize = m_configGroup.readEntry("defaultSize", QApplication::font().pointSize()); - m_defPointSize->setCurrentIndex(QFontDatabase::standardSizes().indexOf(storedSize)); + int sizeIndex = 0; + if (QFontDatabase::standardSizes().contains(storedSize)) { + sizeIndex = QFontDatabase::standardSizes().indexOf(storedSize); + } + m_defPointSize->setCurrentIndex(sizeIndex); int checkedAlignment = m_configGroup.readEntry("defaultAlignment", 0); m_defAlignment = new QButtonGroup(); QHBoxLayout *alignButtons = new QHBoxLayout(); alignButtons->addWidget(m_defPointSize); QToolButton *alignLeft = new QToolButton(); alignLeft->setIcon(KisIconUtils::loadIcon("format-justify-left")); alignLeft->setCheckable(true); alignLeft->setToolTip(i18n("Anchor text to the left.")); m_defAlignment->addButton(alignLeft, 0); alignButtons->addWidget(alignLeft); QToolButton *alignCenter = new QToolButton(); alignCenter->setIcon(KisIconUtils::loadIcon("format-justify-center")); alignCenter->setCheckable(true); m_defAlignment->addButton(alignCenter, 1); alignCenter->setToolTip(i18n("Anchor text to the middle.")); alignButtons->addWidget(alignCenter); QToolButton *alignRight = new QToolButton(); alignRight->setIcon(KisIconUtils::loadIcon("format-justify-right")); alignRight->setCheckable(true); m_defAlignment->addButton(alignRight, 2); alignRight->setToolTip(i18n("Anchor text to the right.")); alignButtons->addWidget(alignRight); m_defAlignment->setExclusive(true); if (checkedAlignment<1) { alignLeft->setChecked(true); } else if (checkedAlignment==1) { alignCenter->setChecked(true); } else if (checkedAlignment==2) { alignRight->setChecked(true); } else { alignLeft->setChecked(true); } defOptionsLayout->addLayout(alignButtons); layout->addWidget(defsOptions); connect(m_defAlignment, SIGNAL(buttonClicked(int)), this, SLOT(storeDefaults())); connect(m_defFont, SIGNAL(currentFontChanged(QFont)), this, SLOT(storeDefaults())); connect(m_defPointSize, SIGNAL(currentIndexChanged(int)), this, SLOT(storeDefaults())); m_edit = new QPushButton(optionWidget); m_edit->setText(i18n("Edit Text")); connect(m_edit, SIGNAL(clicked(bool)), SLOT(showEditor())); layout->addWidget(m_edit); return optionWidget; } KoSelection *SvgTextTool::koSelection() const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0); return canvas()->selectedShapesProxy()->selection(); } KoSvgTextShape *SvgTextTool::selectedShape() const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0); QList shapes = koSelection()->selectedEditableShapes(); if (shapes.isEmpty()) return 0; KIS_SAFE_ASSERT_RECOVER_NOOP(shapes.size() == 1); KoSvgTextShape *textShape = dynamic_cast(shapes.first()); return textShape; } void SvgTextTool::showEditor() { KoSvgTextShape *shape = selectedShape(); if (!shape) return; if (!m_editor) { m_editor = new SvgTextEditor(QApplication::activeWindow()); m_editor->setWindowTitle(i18nc("@title:window", "Krita - Edit Text")); m_editor->setWindowModality(Qt::ApplicationModal); m_editor->setAttribute( Qt::WA_QuitOnClose, false ); connect(m_editor, SIGNAL(textUpdated(KoSvgTextShape*,QString,QString,bool)), SLOT(textUpdated(KoSvgTextShape*,QString,QString,bool))); connect(m_editor, SIGNAL(textEditorClosed()), SLOT(slotTextEditorClosed())); m_editor->activateWindow(); // raise on creation only } m_editor->setShape(shape); m_editor->show(); } void SvgTextTool::textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextUpdated) { SvgTextChangeCommand *cmd = new SvgTextChangeCommand(shape, svg, defs, richTextUpdated); canvas()->addCommand(cmd); } void SvgTextTool::slotTextEditorClosed() { // change tools to the shape selection tool when we close the text editor to allow moving and further editing of the object. // most of the time when we edit text, the shape selection tool is where we left off anyway KoToolManager::instance()->switchToolRequested("InteractionTool"); } QString SvgTextTool::generateDefs() { QString font = m_defFont->currentFont().family(); - QString size = QString::number(QFontDatabase::standardSizes().at(m_defPointSize->currentIndex())); + QString size = QString::number(QFontDatabase::standardSizes().at(m_defPointSize->currentIndex() > -1 ? m_defPointSize->currentIndex() : 0)); + QString textAnchor = "middle"; if (m_defAlignment->button(0)->isChecked()) { textAnchor = "start"; } if (m_defAlignment->button(2)->isChecked()) { textAnchor = "end"; } QString fontColor = canvas()->resourceManager()->foregroundColor().toQColor().name(); return QString("\n \n").arg(font, size, fontColor, textAnchor); } void SvgTextTool::storeDefaults() { m_configGroup = KSharedConfig::openConfig()->group(toolId()); m_configGroup.writeEntry("defaultFont", m_defFont->currentFont().family()); - m_configGroup.writeEntry("defaultSize", QFontDatabase::standardSizes().at(m_defPointSize->currentIndex())); + m_configGroup.writeEntry("defaultSize", QFontDatabase::standardSizes().at(m_defPointSize->currentIndex() > -1 ? m_defPointSize->currentIndex() : 0)); m_configGroup.writeEntry("defaultAlignment", m_defAlignment->checkedId()); } void SvgTextTool::paint(QPainter &gc, const KoViewConverter &converter) { if (!isActivated()) return; KoShape::applyConversion(gc, converter); KisHandlePainterHelper handlePainter(&gc); if (m_dragging) { QPolygonF poly(QRectF(m_dragStart, m_dragEnd)); handlePainter.setHandleStyle(KisHandleStyle::primarySelection()); handlePainter.drawRubberLine(poly); } KoSvgTextShape *shape = selectedShape(); if (shape) { handlePainter.setHandleStyle(KisHandleStyle::primarySelection()); QPainterPath path; path.addRect(shape->boundingRect()); handlePainter.drawPath(path); } if (!m_hoveredShapeHighlightRect.isEmpty()) { handlePainter.setHandleStyle(KisHandleStyle::highlightedPrimaryHandlesWithSolidOutline()); QPainterPath path; path.addRect(m_hoveredShapeHighlightRect); handlePainter.drawPath(path); } } void SvgTextTool::mousePressEvent(KoPointerEvent *event) { KoSvgTextShape *selectedShape = this->selectedShape(); KoSvgTextShape *hoveredShape = dynamic_cast(canvas()->shapeManager()->shapeAt(event->point)); if (!selectedShape || hoveredShape != selectedShape) { canvas()->shapeManager()->selection()->deselectAll(); if (hoveredShape) { canvas()->shapeManager()->selection()->select(hoveredShape); } else { m_dragStart = m_dragEnd = event->point; m_dragging = true; event->accept(); } } } void SvgTextTool::mouseMoveEvent(KoPointerEvent *event) { QRectF updateRect = m_hoveredShapeHighlightRect; if (m_dragging) { m_dragEnd = event->point; m_hoveredShapeHighlightRect = QRectF(); updateRect |= QRectF(m_dragStart, m_dragEnd).normalized().toAlignedRect(); event->accept(); } else { KoSvgTextShape *hoveredShape = dynamic_cast(canvas()->shapeManager()->shapeAt(event->point)); if (hoveredShape) { m_hoveredShapeHighlightRect = hoveredShape->boundingRect(); updateRect |= m_hoveredShapeHighlightRect; } else { m_hoveredShapeHighlightRect = QRect(); } event->ignore(); } if (!updateRect.isEmpty()) { canvas()->updateCanvas(kisGrowRect(updateRect, 100)); } } void SvgTextTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_dragging) { QRectF rectangle = QRectF(m_dragStart, m_dragEnd).normalized(); if (rectangle.width() < 4 && rectangle.height() < 4) { m_dragging = false; event->accept(); return; } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID"); KoProperties *params = new KoProperties();//Fill these with "svgText", "defs" and "shapeRect" params->setProperty("defs", QVariant(generateDefs())); if (m_dragging) { m_dragEnd = event->point; m_dragging = false; //The following show only happen when we're creating preformatted text. If we're making //Word-wrapped text, it should take the rectangle unmodified. - int size = QFontDatabase::standardSizes().at(m_defPointSize->currentIndex()); + int size = QFontDatabase::standardSizes().at(m_defPointSize->currentIndex() > -1 ? m_defPointSize->currentIndex() : 0); QFont font = m_defFont->currentFont(); font.setPointSize(size); rectangle.setTop(rectangle.top()+QFontMetrics(font).lineSpacing()); if (m_defAlignment->button(1)->isChecked()) { rectangle.setLeft(rectangle.center().x()); } else if (m_defAlignment->button(2)->isChecked()) { qreal right = rectangle.right(); rectangle.setRight(right+10); rectangle.setLeft(right); } params->setProperty("shapeRect", QVariant(rectangle)); } KoShape *textShape = factory->createShape( params, canvas()->shapeController()->resourceManager()); KUndo2Command *parentCommand = new KUndo2Command(); new KoKeepShapesSelectedCommand(koSelection()->selectedShapes(), {}, canvas()->selectedShapesProxy(), false, parentCommand); KUndo2Command *cmd = canvas()->shapeController()->addShape(textShape, 0, parentCommand); parentCommand->setText(cmd->text()); new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand); canvas()->addCommand(parentCommand); showEditor(); event->accept(); } else if (m_editor) { showEditor(); event->accept(); } } void SvgTextTool::keyPressEvent(QKeyEvent *event) { if (event->key()==Qt::Key_Enter || event->key()==Qt::Key_Return) { showEditor(); event->accept(); } else { event->ignore(); } } void SvgTextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != selectedShape()) { event->ignore(); // allow the event to be used by another return; } showEditor(); if(m_editor) { m_editor->raise(); m_editor->activateWindow(); } event->accept(); }