diff --git a/CMakeLists.txt b/CMakeLists.txt index fc648ff4c6..d427f469d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,842 +1,842 @@ 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 Sql ) 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 ) 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/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/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/libs/flake/KoClipMask.cpp b/libs/flake/KoClipMask.cpp index 7e2eb17bed..189984e003 100644 --- a/libs/flake/KoClipMask.cpp +++ b/libs/flake/KoClipMask.cpp @@ -1,174 +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 : public QSharedData { - Private() {} + 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) { - // XXX: Use KisDescendent instead of this 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::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/KoClipPath.cpp b/libs/flake/KoClipPath.cpp index 2ce35345a9..4a2ffe4817 100644 --- a/libs/flake/KoClipPath.cpp +++ b/libs/flake/KoClipPath.cpp @@ -1,236 +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 : 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() { } 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/KoConnectionShape.cpp b/libs/flake/KoConnectionShape.cpp index 490023a0f4..ae6d46d22a 100644 --- a/libs/flake/KoConnectionShape.cpp +++ b/libs/flake/KoConnectionShape.cpp @@ -1,779 +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 KoConnectionShape::Private::Private() : QSharedData() - , shape1(0), - shape2(0), - connectionPointId1(-1), - connectionPointId2(-1), - connectionType(KoConnectionShape::Standard), - forceUpdate(false), - hasCustomPath(false) + , shape1(0) + , shape2(0) + , connectionPointId1(-1) + , connectionPointId2(-1) + , connectionType(KoConnectionShape::Standard) + , forceUpdate(false) + , hasCustomPath(false) { } 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) + , 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 KoConnectionShape::escapeDirection(int handleId) const { QPointF direction; 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 = 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 = d->crossProd(p1-centerPoint, p2-centerPoint); // check on which side of the first sector edge our handle point is 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 = 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 = 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 = 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 = absoluteTransformation(0).inverted(); direction = invMatrix.map(direction) - invMatrix.map(QPointF()); direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); } return direction; } 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 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 KoConnectionShape::normalPath(const qreal MinimumEscapeLength) { // Clear the path to build it again. 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; edges1.append(edgePoint1); edges2.prepend(edgePoint2); 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 (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 = 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 = d->perpendicularDirection(edgePoint1, direction1, edgePoint2); } else { // we are not going into the same direction, so switch direction direction1 = d->perpendicularDirection(edgePoint1, direction1, edgePoint2); } } } d->path.append(edges1); d->path.append(edges2); d->path.append(handles()[KoConnectionShape::EndHandle]); } qreal KoConnectionShape::Private::scalarProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.x() + v1.y() * v2.y(); } qreal KoConnectionShape::Private::crossProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.y() - v1.y() * v2.x(); } 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() { 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 (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 (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() , d(new Private) { handles().push_back(QPointF(0, 0)); handles().push_back(QPointF(140, 140)); moveTo(handles()[StartHandle]); lineTo(handles()[EndHandle]); updatePath(QSizeF(140, 140)); clearConnectionPoints(); } KoConnectionShape::KoConnectionShape(const KoConnectionShape &rhs) : KoParameterShape(rhs) , d(rhs.d) { } KoConnectionShape::~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 { 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(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(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) { 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"; 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 { 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"; 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 { 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 (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()); 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() { 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 = 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 = handles()[EndHandle]; } 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()); 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); if (handleId >= handles().size()) return; handles()[handleId] = point; } void KoConnectionShape::updatePath(const QSizeF &size) { Q_UNUSED(size); const qreal MinimumEscapeLength = (qreal)20.; clear(); switch (d->connectionType) { case Standard: { 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 = escapeDirection(0); QPointF direction2 = escapeDirection(handles().count() - 1); moveTo(handles()[StartHandle]); if (! direction1.isNull()) lineTo(handles()[StartHandle] + MinimumEscapeLength * direction1); if (! direction2.isNull()) lineTo(handles()[EndHandle] + MinimumEscapeLength * direction2); lineTo(handles()[EndHandle]); break; } case Straight: moveTo(handles()[StartHandle]); lineTo(handles()[EndHandle]); break; case Curve: // TODO QPointF direction1 = escapeDirection(0); QPointF direction2 = escapeDirection(handles().count() - 1); moveTo(handles()[StartHandle]); if (! direction1.isNull() && ! direction2.isNull()) { QPointF curvePoint1 = handles()[StartHandle] + 5.0 * MinimumEscapeLength * direction1; QPointF curvePoint2 = handles()[EndHandle] + 5.0 * MinimumEscapeLength * direction2; curveTo(curvePoint1, curvePoint2, handles()[EndHandle]); } else { lineTo(handles()[EndHandle]); } break; } normalize(); } bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId) { // 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) { // 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 { return d->shape1; } int KoConnectionShape::firstConnectionId() const { return d->connectionPointId1; } KoShape *KoConnectionShape::secondShape() const { return d->shape2; } int KoConnectionShape::secondConnectionId() const { return d->connectionPointId2; } KoConnectionShape::Type KoConnectionShape::type() const { return d->connectionType; } void KoConnectionShape::setType(Type connectionType) { d->connectionType = connectionType; updatePath(size()); } void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape) { 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/KoSelection_p.h b/libs/flake/KoSelection_p.h index 796232fc76..5fa1ce5579 100644 --- a/libs/flake/KoSelection_p.h +++ b/libs/flake/KoSelection_p.h @@ -1,46 +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 #include "kis_thread_safe_signal_compressor.h" class KoShapeGroup; class KoSelection::Private : public QSharedData { public: explicit Private() - : activeLayer(0) + : QSharedData() + , activeLayer(0) , selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE) {} - explicit Private(const Private &rhs) - : activeLayer(0) + explicit Private(const Private &) + : QSharedData() + , activeLayer(0) , selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE) { } QList selectedShapes; KoShapeLayer *activeLayer; KisThreadSafeSignalCompressor selectionChangedCompressor; }; #endif diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 1555124d33..9a88c382c3 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2425 +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 // KoShape::Private KoShape::Private::Private() - : 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) + : 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); } 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) + , 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 KoShape::shapeChangedPriv(KoShape::ChangeType type) { if (d->parent) d->parent->model()->childChanged(this, type); this->shapeChanged(type); Q_FOREACH (KoShape * shape, d->dependees) { shape->shapeChanged(type, this); } Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { listener->notifyShapeChangedImpl(type, this); } } void KoShape::addShapeManager(KoShapeManager *manager) { d->shapeManagers.insert(manager); } void KoShape::removeShapeManager(KoShapeManager *manager) { d->shapeManagers.remove(manager); } 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 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 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(new Private) { notifyChanged(); } KoShape::KoShape(const KoShape &rhs) : d(rhs.d) { } KoShape::~KoShape() { shapeChangedPriv(Deleted); d->listeners.clear(); /** * 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) { 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(); shapeChangedPriv(ScaleChanged); } void KoShape::rotate(qreal angle) { 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(); shapeChangedPriv(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { 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(); shapeChangedPriv(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { QSizeF oldSize(size()); // always set size, as d->size and size() may vary setSizeImpl(newSize); if (oldSize == newSize) return; notifyChanged(); shapeChangedPriv(SizeChanged); } void KoShape::setSizeImpl(const QSizeF &size) const { d->size = size; } void KoShape::setPosition(const QPointF &newPosition) { 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(); shapeChangedPriv(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { 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 { 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 { 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) { d->localMatrix = matrix * d->localMatrix; notifyChanged(); shapeChangedPriv(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { d->localMatrix = matrix; notifyChanged(); shapeChangedPriv(GenericMatrixChange); } QTransform KoShape::transformation() const { 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) { 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(); 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 { return d->zIndex; } void KoShape::update() const { 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; } 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) { QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); shapeChangedPriv(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { 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->localMatrix; } void KoShape::notifyChanged() { Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { return d->userData.data(); } bool KoShape::hasTransparency() const { QSharedPointer bg = background(); return !bg || bg->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { d->transparency = qBound(0.0, transparency, 1.0); shapeChangedPriv(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { 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 { KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { // 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 { return d->size; } QPointF KoShape::position() const { QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { // 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) { 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) shapeChangedPriv(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { 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) { d->connectors.remove(connectionPointId); shapeChangedPriv(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); shapeChangedPriv(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { d->inheritBackground = false; d->fill = fill; shapeChangedPriv(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { QSharedPointer bg; if (!d->inheritBackground) { bg = d->fill; } else if (parent()) { bg = parent()->background(); } return bg; } void KoShape::setInheritBackground(bool value) { d->inheritBackground = value; if (d->inheritBackground) { d->fill.clear(); } } bool KoShape::inheritBackground() const { return d->inheritBackground; } void KoShape::setZIndex(qint16 zIndex) { if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { d->runThrough = runThrough; } void KoShape::setVisible(bool on) { int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { 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) { d->printable = on; } bool KoShape::isPrintable() const { if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { d->selectable = selectable; } bool KoShape::isSelectable() const { return d->selectable; } void KoShape::setGeometryProtected(bool on) { d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { d->protectContent = protect; } bool KoShape::isContentProtected() const { return d->protectContent; } KoShapeContainer *KoShape::parent() const { return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { d->keepAspect = keepAspect; shapeChangedPriv(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { return d->keepAspect; } QString KoShape::shapeId() const { return d->shapeId; } void KoShape::setShapeId(const QString &id) { d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { d->detectCollision = detect; } bool KoShape::collisionDetection() { return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { KoShapeStrokeModelSP stroke; if (!d->inheritStroke) { stroke = d->stroke; } else if (parent()) { stroke = parent()->stroke(); } return stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { d->inheritStroke = false; d->stroke = stroke; shapeChangedPriv(StrokeChanged); notifyChanged(); } void KoShape::setInheritStroke(bool value) { d->inheritStroke = value; if (d->inheritStroke) { d->stroke.clear(); } } bool KoShape::inheritStroke() const { return d->inheritStroke; } void KoShape::setShadow(KoShapeShadow *shadow) { if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } shapeChangedPriv(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { return d->shadow; } void KoShape::setBorder(KoBorder *border) { if (d->border) { // The shape owns the border. delete d->border; } d->border = border; shapeChangedPriv(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { d->clipPath.reset(clipPath); shapeChangedPriv(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { return d->clipMask.data(); } QTransform KoShape::transform() const { return d->localMatrix; } QString KoShape::name() const { return d->name; } void KoShape::setName(const QString &name) { d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isShapeEditable(bool recursive) const { 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 { // 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) { 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 = 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 = 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 = 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 *KoShape::Private::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); 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 *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) { 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) { 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 { 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 { // 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 { 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) { 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) { int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { return d->dependees.contains(shape); } QList KoShape::dependees() const { 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) { d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { d->toolDelegates = delegates; } QString KoShape::hyperLink () const { return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { d->hyperLink = hyperLink; } 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) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { 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/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp index f57c5517ff..d00a82171d 100644 --- a/libs/flake/KoShapeContainer.cpp +++ b/libs/flake/KoShapeContainer.cpp @@ -1,231 +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" KoShapeContainer::Private::Private(KoShapeContainer *q) - : shapeInterface(q), - model(0) + : shapeInterface(q) + , model(0) { } KoShapeContainer::Private::~Private() { delete model; } KoShapeContainer::Private::Private(const KoShapeContainer::Private &rhs, KoShapeContainer *q) : shapeInterface(q) , model(0) { Q_UNUSED(rhs); } KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model) : KoShape() , d(new Private(this)) { d->model = model; } KoShapeContainer::KoShapeContainer(const KoShapeContainer &rhs) : KoShape(rhs) , d(new Private(*(rhs.d.data()), this)) { } KoShapeContainer::~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 { if (d->model == 0) return 0; return d->model->count(); } void KoShapeContainer::setClipped(const KoShape *child, bool clipping) { if (d->model == 0) return; d->model->setClipped(child, clipping); } void KoShapeContainer::setInheritsTransform(const KoShape *shape, bool inherit) { if (d->model == 0) return; d->model->setInheritsTransform(shape, inherit); } bool KoShapeContainer::inheritsTransform(const KoShape *shape) const { 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); 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 { if (d->model == 0) // throw exception?? return false; return d->model->isClipped(child); } void KoShapeContainer::update() const { KoShape::update(); if (d->model) Q_FOREACH (KoShape *shape, d->model->shapes()) shape->update(); } QList KoShapeContainer::shapes() const { if (d->model == 0) return QList(); return d->model->shapes(); } KoShapeContainerModel *KoShapeContainer::model() const { 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() { return &d->shapeInterface; } KoShapeContainer::ShapeInterface::ShapeInterface(KoShapeContainer *_q) : q(_q) { } void KoShapeContainer::ShapeInterface::addShape(KoShape *shape) { 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) { 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_p.h b/libs/flake/KoShapeContainer_p.h index 19f8ceb644..7564ff7e29 100644 --- a/libs/flake/KoShapeContainer_p.h +++ b/libs/flake/KoShapeContainer_p.h @@ -1,44 +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 "KoShapeContainer.h" #include "kritaflake_export.h" -#include - class KoShapeContainerModel; /** * \internal used private d-pointer class for the \a KoShapeContainer class. */ -class KRITAFLAKE_EXPORT KoShapeContainer::Private : public QSharedData +class KRITAFLAKE_EXPORT KoShapeContainer::Private { public: explicit Private(KoShapeContainer *q); virtual ~Private(); Private(const Private &rhs, KoShapeContainer *q); KoShapeContainer::ShapeInterface shapeInterface; KoShapeContainerModel *model; }; #endif diff --git a/libs/flake/KoTosContainer.cpp b/libs/flake/KoTosContainer.cpp index 15b6af707e..83f26535b1 100644 --- a/libs/flake/KoTosContainer.cpp +++ b/libs/flake/KoTosContainer.cpp @@ -1,347 +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 KoTosContainer::Private::Private() - : resizeBehavior(KoTosContainer::IndependentSizes) + : QSharedData() + , resizeBehavior(KoTosContainer::IndependentSizes) { } KoTosContainer::Private::Private(const Private &rhs) - : resizeBehavior(rhs.resizeBehavior) + : QSharedData() + , resizeBehavior(rhs.resizeBehavior) , preferredTextRect(rhs.preferredTextRect) , alignment(rhs.alignment) { } KoTosContainer::Private::~Private() { } KoTosContainer::KoTosContainer() : KoShapeContainer() , d(new Private) { } KoTosContainer::KoTosContainer(const KoTosContainer &rhs) : KoShapeContainer(rhs) , d(rhs.d) { } KoTosContainer::~KoTosContainer() { delete textShape(); } void KoTosContainer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &) { } bool KoTosContainer::loadText(const KoXmlElement &element, KoShapeLoadingContext &context) { 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) { 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) { if (d->resizeBehavior == resizeBehavior) { return; } d->resizeBehavior = resizeBehavior; if (model()) { model()->containerChanged(this, KoShape::SizeChanged); } } KoTosContainer::ResizeBehavior KoTosContainer::resizeBehavior() const { return d->resizeBehavior; } void KoTosContainer::setTextAlignment(Qt::Alignment alignment) { 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) { 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 { return d->preferredTextRect; } KoShape *KoTosContainer::createTextShape(KoDocumentResourceManager *documentResources) { if (!documentResources) { warnFlake << "KoDocumentResourceManager not found"; return 0; } delete textShape(); delete model(); 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); if (model() == 0) { return; } if (type == SizeChanged || type == ContentChanged) { model()->containerChanged(this, type); } // TODO is this needed? #if 0 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/text/KoSvgTextChunkShape.cpp b/libs/flake/text/KoSvgTextChunkShape.cpp index 9d6b785866..15122653de 100644 --- a/libs/flake/text/KoSvgTextChunkShape.cpp +++ b/libs/flake/text/KoSvgTextChunkShape.cpp @@ -1,984 +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 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 KoSvgTextChunkShape::Private::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface { LayoutInterface(KoSvgTextChunkShape *_q) : q(_q) {} KoSvgText::AutoValue textLength() const override { return q->d->textLength; } KoSvgText::LengthAdjust lengthAdjust() const override { return q->d->lengthAdjust; } int numChars() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0); int result = 0; if (!q->shapeCount()) { 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->text.isEmpty(), false); return !q->shapeCount(); } QString nodeText() const override { 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->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->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->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->associatedOutline; path.setFillRule(Qt::WindingFill); path = path.simplified(); q->d->associatedOutline = path; q->setSize(path.boundingRect().size()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } void clearAssociatedOutline() override { q->d->associatedOutline = QPainterPath(); q->setSize(QSizeF()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } private: KoSvgTextChunkShape *q; }; KoSvgTextChunkShape::KoSvgTextChunkShape() : KoShapeContainer() , d(new Private) { d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs) : KoShapeContainer(rhs) , d(rhs.d) { 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 { 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) { // 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) { 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 KoSvgTextChunkShape::Private::loadContextBasedProperties(SvgGraphicsContext *gc) { properties = gc->textProperties; font = gc->font; fontFamiliesList = gc->fontFamiliesList; } void KoSvgTextChunkShape::resetTextShape() { 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) { 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) { 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() { 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 { 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 { return d->layoutInterface->isTextNode(); } KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface() { return d->layoutInterface.data(); } bool KoSvgTextChunkShape::isRichTextPreferred() const { return isRootTextNode() && d->isRichTextPreferred; } void KoSvgTextChunkShape::setRichTextPreferred(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRootTextNode()); d->isRichTextPreferred = value; } bool KoSvgTextChunkShape::isRootTextNode() const { return false; } /**************************************************************************************************/ /* KoSvgTextChunkShape::Private */ /**************************************************************************************************/ #include "SimpleShapeContainerModel.h" KoSvgTextChunkShape::Private::Private() : QSharedData() { } KoSvgTextChunkShape::Private::Private(const Private &rhs) - : properties(rhs.properties), - font(rhs.font), - fontFamiliesList(rhs.fontFamiliesList), - localTransformations(rhs.localTransformations), - textLength(rhs.textLength), - lengthAdjust(rhs.lengthAdjust), - text(rhs.text), - isRichTextPreferred(rhs.isRichTextPreferred) + : 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) { } KoSvgTextChunkShape::Private::~Private() { } #include #include #include KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShape::fetchCharFormat() const { KoSvgText::KoSvgCharChunkFormat format; format.setFont(d->font); format.setTextAnchor(KoSvgText::TextAnchor(d->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt())); KoSvgText::Direction direction = KoSvgText::Direction(d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); format.setLayoutDirection(direction == KoSvgText::DirectionLeftToRight ? Qt::LeftToRight : Qt::RightToLeft); KoSvgText::BaselineShiftMode shiftMode = 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 = d->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value(); if (!letterSpacing.isAuto) { format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing.customValue); } KoSvgText::AutoValue wordSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value(); if (!wordSpacing.isAuto) { format.setFontWordSpacing(wordSpacing.customValue); } 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 (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 (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(this)); return format; } void KoSvgTextChunkShape::applyParentCharTransformations(const QVector transformations) { if (shapeCount()) { int numCharsPassed = 0; 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->applyParentCharTransformations(t); numCharsPassed += numCharsInSubtree; if (numCharsPassed >= transformations.size()) break; } } else { for (int i = 0; i < qMin(transformations.size(), d->text.size()); i++) { KIS_SAFE_ASSERT_RECOVER_RETURN(d->localTransformations.size() >= i); if (d->localTransformations.size() == i) { d->localTransformations.append(transformations[i]); } else { d->localTransformations[i].mergeInParentTransformation(transformations[i]); } } } } diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp index 6166d9ccd6..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 #include class KoSvgTextShape::Private : public QSharedData { public: Private() : QSharedData() { } - Private(const Private &rhs) + Private(const Private &) : QSharedData() { } std::vector> cachedLayouts; std::vector cachedLayoutsOffsets; QThread *cachedLayoutsWorkingThread = 0; void clearAssociatedOutlines(KoShape *rootShape); }; KoSvgTextShape::KoSvgTextShape() : KoSvgTextChunkShape() , d(new Private) { setShapeId(KoSvgTextShape_SHAPEID); } KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs) : 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_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() { 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() { 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 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/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_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..8ba041bd60 100644 --- a/libs/pigment/KoColorSpaceRegistry.cpp +++ b/libs/pigment/KoColorSpaceRegistry.cpp @@ -1,849 +1,847 @@ /* * 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::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/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/resourcewidgets/KisTagChooserWidget.cpp b/libs/resourcewidgets/KisTagChooserWidget.cpp index eb2e17cfed..6bba494aae 100644 --- a/libs/resourcewidgets/KisTagChooserWidget.cpp +++ b/libs/resourcewidgets/KisTagChooserWidget.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 "KisTagChooserWidget.h" #include #include #include #include #include #include #include "KisResourceItemChooserContextMenu.h" #include "KisTagToolButton.h" class Q_DECL_HIDDEN KisTagChooserWidget::Private { public: KisSqueezedComboBox *comboBox; KisTagToolButton *tagToolButton; QStringList readOnlyTags; QStringList tags; }; KisTagChooserWidget::KisTagChooserWidget(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 KisTagToolButton(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())); } KisTagChooserWidget::~KisTagChooserWidget() { delete d; } void KisTagChooserWidget::contextDeleteCurrentTag() { if (selectedTagIsReadOnly()) { return; } emit tagDeletionRequested(currentlySelectedTag()); } +void KisTagChooserWidget::tagChanged(int) +{ + emit tagChosen(d->comboBox->currentUnsqueezedText()); +} + void KisTagChooserWidget::tagRenamingRequested(const QString& newName) { if (newName.isEmpty() || selectedTagIsReadOnly()) { return; } emit tagRenamingRequested(currentlySelectedTag(), newName); } void KisTagChooserWidget::setUndeletionCandidate(const QString& tag) { d->tagToolButton->setUndeletionCandidate(tag); } void KisTagChooserWidget::setCurrentIndex(int index) { d->comboBox->setCurrentIndex(index); } int KisTagChooserWidget::findIndexOf(QString tagName) { return d->comboBox->findOriginalText(tagName); } void KisTagChooserWidget::addReadOnlyItem(QString tagName) { d->readOnlyTags.append(tagName); } void KisTagChooserWidget::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 KisTagChooserWidget::currentlySelectedTag() { - return d->comboBox->itemHighlighted(); + return d->comboBox->currentUnsqueezedText(); } QStringList KisTagChooserWidget::allTags() { return d->tags; } bool KisTagChooserWidget::selectedTagIsReadOnly() { - return d->readOnlyTags.contains(d->comboBox->itemHighlighted()) ; + return d->readOnlyTags.contains(d->comboBox->currentUnsqueezedText()) ; } void KisTagChooserWidget::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 KisTagChooserWidget::clear() { d->comboBox->resetOriginalTexts(QStringList()); } void KisTagChooserWidget::removeItem(QString item) { int pos = findIndexOf(item); if (pos >= 0) { d->comboBox->removeSqueezedItem(pos); d->tags.removeOne(item); } } void KisTagChooserWidget::tagOptionsContextMenuAboutToShow() { /* only enable the save button if the selected tag set is editable */ d->tagToolButton->readOnlyMode(selectedTagIsReadOnly()); emit popupMenuAboutToShow(); } void KisTagChooserWidget::showTagToolButton(bool show) { d->tagToolButton->setVisible(show); } diff --git a/libs/resourcewidgets/KisTagChooserWidget.h b/libs/resourcewidgets/KisTagChooserWidget.h index c25c990428..372f613912 100644 --- a/libs/resourcewidgets/KisTagChooserWidget.h +++ b/libs/resourcewidgets/KisTagChooserWidget.h @@ -1,74 +1,75 @@ /* * 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 * Copyright (c) 2019 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISTAGCHOOSERWIDGET_H #define KISTAGCHOOSERWIDGET_H #include #include "kritaresourcewidgets_export.h" class KRITARESOURCEWIDGETS_EXPORT KisTagChooserWidget : public QWidget { Q_OBJECT public: explicit KisTagChooserWidget(QWidget* parent); ~KisTagChooserWidget() 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/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 69feaa903c..3783a5ac2b 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2647 +1,2655 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include "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}; KisResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; + QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; 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)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(updateWindowMenu())); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); 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(); KisWorkspaceResourceSP 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(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), 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(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } 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(KisWorkspaceResourceSP 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, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); 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(); } 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/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 3bac1a2ebe..16fbcd8c77 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1764 +1,1764 @@ /* * 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 #include "KisProofingConfiguration.h" #include "KoColorConversionTransformation.h" #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "kis_action_registry.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_color_manager.h" #include "kis_config.h" #include "kis_cursor.h" #include "kis_image_config.h" #include "kis_preference_set_registry.h" #include "widgets/kis_cmb_idlist.h" #include #include "kis_file_name_requester.h" #include #include #include #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)); // // Resources // m_urlCacheDbLocation->setMode(KoFileDialog::OpenDirectory); m_urlCacheDbLocation->setConfigurationName("cachedb_location"); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setMode(KoFileDialog::OpenDirectory); m_urlResourceFolder->setConfigurationName("resource_directory"); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } 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); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } 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 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(); 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 { cmbPreferredRenderer->setEnabled(false); } if (supportedRenderers & KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #ifdef Q_OS_WIN 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 (!(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()); cfg.writeEntry(KisResourceCacheDb::dbLocationKey, dialog->m_general->m_urlCacheDbLocation->fileName()); cfg.writeEntry(KisResourceLocator::resourceLocationKey, dialog->m_general->m_urlResourceFolder->fileName()); // 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(); { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbPreferredRenderer->itemData( dialog->m_displaySettings->cmbPreferredRenderer->currentIndex()).toInt()); KisOpenGL::setUserPreferredOpenGLRendererConfig(renderer); } 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 ec4d98515f..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(image, SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotUpdateDocumentResolution())); - m_d->imageConnections.addConnection(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_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index 14e76f18cf..e357c7633c 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,829 +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, "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::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 *touchEvent = static_cast (event); KisAbstractInputAction::setInputManager(this); 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 *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 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 *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) { // 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/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp index 1d9ed49f51..e042785ec4 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.cpp +++ b/libs/ui/opengl/KisOpenGLModeProber.cpp @@ -1,327 +1,327 @@ /* * 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; }; struct EnvironmentSetter { EnvironmentSetter(const QLatin1String &env, const QString &value) : m_env(env) { if (qEnvironmentVariableIsEmpty(m_env.latin1())) { - m_oldValue = qEnvironmentVariable(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 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/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc index 6c717de640..f343021478 100644 --- a/libs/ui/widgets/kis_color_space_selector.cc +++ b/libs/ui/widgets/kis_color_space_selector.cc @@ -1,270 +1,270 @@ /* * 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(slotModelsComboBoxActivated(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), this, SLOT(slotDepthsComboBoxActivated())); connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(QString)), 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->itemHighlighted(); + 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); } } 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); fillCmbDepths(id); } void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); 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()); } } } 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/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/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/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index 6240e4c0aa..cdb2e77649 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 Qt::UserRole + KisResourceModel::LargeThumbnail: { 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 avialable 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/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/snapshotdocker/SnapshotDocker.cpp b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp index 5cc4661c6a..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("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 53fe6cf380..de14017e60 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(QSharedPointer resource); private: QSharedPointer 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/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/exr/exr_converter.cc b/plugins/impex/exr/exr_converter.cc index a8e4867a1a..ec3edd7176 100644 --- a/plugins/impex/exr/exr_converter.cc +++ b/plugins/impex/exr/exr_converter.cc @@ -1,1404 +1,1402 @@ /* * Copyright (c) 2005 Adrian Page * 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 "exr_converter.h" #include #include #include #include #include #include #include "exr_extra_tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include "kis_kra_savexml_visitor.h" #include // Do not translate! #define HDR_LAYER "HDR Layer" template struct Rgba { _T_ r; _T_ g; _T_ b; _T_ a; }; struct ExrGroupLayerInfo; struct ExrLayerInfoBase { ExrLayerInfoBase() : colorSpace(0), parent(0) { } const KoColorSpace* colorSpace; QString name; const ExrGroupLayerInfo* parent; }; struct ExrGroupLayerInfo : public ExrLayerInfoBase { ExrGroupLayerInfo() : groupLayer(0) {} KisGroupLayerSP groupLayer; }; enum ImageType { IT_UNKNOWN, IT_FLOAT16, IT_FLOAT32, IT_UNSUPPORTED }; struct ExrPaintLayerInfo : public ExrLayerInfoBase { ExrPaintLayerInfo() : imageType(IT_UNKNOWN) { } ImageType imageType; QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name struct Remap { Remap(const QString& _original, const QString& _current) : original(_original), current(_current) { } QString original; QString current; }; QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita void updateImageType(ImageType channelType); }; void ExrPaintLayerInfo::updateImageType(ImageType channelType) { if (imageType == IT_UNKNOWN) { imageType = channelType; } else if (imageType != channelType) { imageType = IT_UNSUPPORTED; } } struct ExrPaintLayerSaveInfo { QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.") KisPaintDeviceSP layerDevice; KisPaintLayerSP layer; QList channels; Imf::PixelType pixelType; }; struct EXRConverter::Private { Private() : doc(0) , alphaWasModified(false) , showNotifications(false) {} KisImageSP image; KisDocument *doc; bool alphaWasModified; bool showNotifications; QString errorMessage; template void unmultiplyAlpha(typename WrapperType::pixel_type *pixel); template void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); template void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); QDomDocument loadExtraLayersInfo(const Imf::Header &header); bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames); void makeLayerNamesUnique(QList& informationObjects); void recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent); void reportLayersNotSaved(const QSet &layersNotSaved); QString fetchExtraLayersInfo(QList& informationObjects); }; EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications) : d(new Private) { d->doc = doc; d->showNotifications = showNotifications; // Set thread count for IlmImf library Imf::setGlobalThreadCount(QThread::idealThreadCount()); dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount(); } EXRConverter::~EXRConverter() { } ImageType imfTypeToKisType(Imf::PixelType type) { switch (type) { case Imf::UINT: case Imf::NUM_PIXELTYPES: return IT_UNSUPPORTED; case Imf::HALF: return IT_FLOAT16; case Imf::FLOAT: return IT_FLOAT32; default: qFatal("Out of bound enum"); return IT_UNKNOWN; } } const KoColorSpace *kisTypeToColorSpace(QString colorModelID, ImageType imageType) { QString colorDepthID = "UNKNOWN"; switch(imageType) { case IT_FLOAT16: colorDepthID = Float16BitsColorDepthID.id(); break; case IT_FLOAT32: colorDepthID = Float32BitsColorDepthID.id(); break; default: return 0; }; const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID); const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); return KoColorSpaceRegistry::instance()->colorSpace(colorModelID, colorDepthID, profileName); } template static inline T alphaEpsilon() { return static_cast(HALF_EPSILON); } template static inline T alphaNoiseThreshold() { return static_cast(0.01); // 1% } static inline bool qFuzzyCompare(half p1, half p2) { return std::abs(p1 - p2) < float(HALF_EPSILON); } static inline bool qFuzzyIsNull(half h) { return std::abs(h) < float(HALF_EPSILON); } template struct RgbPixelWrapper { typedef T channel_type; typedef Rgba pixel_type; RgbPixelWrapper(Rgba &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.a; } inline bool checkMultipliedColorsConsistent() const { return !(std::abs(pixel.a) < alphaEpsilon() && (!qFuzzyIsNull(pixel.r) || !qFuzzyIsNull(pixel.g) || !qFuzzyIsNull(pixel.b))); } inline bool checkUnmultipliedColorsConsistent(const Rgba &mult) const { const T alpha = std::abs(pixel.a); return alpha >= alphaNoiseThreshold() || (qFuzzyCompare(T(pixel.r * alpha), mult.r) && qFuzzyCompare(T(pixel.g * alpha), mult.g) && qFuzzyCompare(T(pixel.b * alpha), mult.b)); } inline void setUnmultiplied(const Rgba &mult, T newAlpha) { const T absoluteAlpha = std::abs(newAlpha); pixel.r = mult.r / absoluteAlpha; pixel.g = mult.g / absoluteAlpha; pixel.b = mult.b / absoluteAlpha; pixel.a = newAlpha; } Rgba &pixel; }; template struct GrayPixelWrapper { typedef T channel_type; typedef typename KoGrayTraits::Pixel pixel_type; GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.alpha; } inline bool checkMultipliedColorsConsistent() const { return !(std::abs(pixel.alpha) < alphaEpsilon() && !qFuzzyIsNull(pixel.gray)); } inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const { const T alpha = std::abs(pixel.alpha); return alpha >= alphaNoiseThreshold() || qFuzzyCompare(T(pixel.gray * alpha), mult.gray); } inline void setUnmultiplied(const pixel_type &mult, T newAlpha) { const T absoluteAlpha = std::abs(newAlpha); pixel.gray = mult.gray / absoluteAlpha; pixel.alpha = newAlpha; } pixel_type &pixel; }; template void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel) { typedef typename WrapperType::pixel_type pixel_type; typedef typename WrapperType::channel_type channel_type; WrapperType srcPixel(*pixel); if (!srcPixel.checkMultipliedColorsConsistent()) { channel_type newAlpha = srcPixel.alpha(); pixel_type __dstPixelData; WrapperType dstPixel(__dstPixelData); /** * Division by a tiny alpha may result in an overflow of half * value. That is why we use safe iterational approach. */ while (1) { dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha); if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) { break; } newAlpha += alphaEpsilon(); alphaWasModified = true; } *pixel = dstPixel.pixel; } else if (srcPixel.alpha() > 0.0) { srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha()); } } template void multiplyAlpha(Pixel *pixel) { if (alphaPos >= 0) { T alpha = pixel->data[alphaPos]; if (alpha > 0.0) { for (int i = 0; i < size; ++i) { if (i != alphaPos) { pixel->data[i] *= alpha; } } pixel->data[alphaPos] = alpha; } } } template void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef Rgba<_T_> Rgba; QVector pixels(width * height); bool hasAlpha = info.channelMap.contains("A"); Imf::FrameBuffer frameBuffer; Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["R"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->r, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->g, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["B"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->b, sizeof(Rgba) * 1, sizeof(Rgba) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->a, sizeof(Rgba) * 1, sizeof(Rgba) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); Rgba *rgba = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); while (it.nextPixel()) { if (hasAlpha) { unmultiplyAlpha >(rgba); } typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast::Pixel*>(it.rawData()); dst->red = rgba->r; dst->green = rgba->g; dst->blue = rgba->b; if (hasAlpha) { dst->alpha = rgba->a; } else { dst->alpha = 1.0; } ++rgba; } } template void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef typename GrayPixelWrapper<_T_>::channel_type channel_type; typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type; KIS_ASSERT_RECOVER_RETURN( layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID); QVector pixels(width * height); Q_ASSERT(info.channelMap.contains("G")); dbgFile << "G -> " << info.channelMap["G"]; bool hasAlpha = info.channelMap.contains("A"); dbgFile << "Has Alpha:" << hasAlpha; Imf::FrameBuffer frameBuffer; pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->gray, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->alpha, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); pixel_type *srcPtr = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); do { if (hasAlpha) { unmultiplyAlpha >(srcPtr); } pixel_type* dstPtr = reinterpret_cast(it.rawData()); dstPtr->gray = srcPtr->gray; dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0); ++srcPtr; } while (it.nextPixel()); } bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2) { if (idx1 > idx2) return true; if (group.name == list[idx2]) { return recCheckGroup(*group.parent, list, idx1, idx2 - 1); } return false; } ExrGroupLayerInfo* searchGroup(QList* groups, QStringList list, int idx1, int idx2) { if (idx1 > idx2) { return 0; } // Look for the group for (int i = 0; i < groups->size(); ++i) { if (recCheckGroup(groups->at(i), list, idx1, idx2)) { return &(*groups)[i]; } } // Create the group ExrGroupLayerInfo info; info.name = list.at(idx2); info.parent = searchGroup(groups, list, idx1, idx2 - 1); groups->append(info); return &groups->last(); } QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header) { const Imf::StringAttribute *layersInfoAttribute = header.findTypedAttribute(EXR_KRITA_LAYERS); if (!layersInfoAttribute) return QDomDocument(); QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str()); QDomDocument doc; doc.setContent(layersInfoString); return doc; } bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames) { std::set extraInfoLayers; QDomElement root = doc.documentElement(); KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; }; QDomElement el = root.firstChildElement(); while(!el.isNull()) { KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; }; QString layerName = el.attribute(EXR_NAME).toUtf8(); if (layerName != QString(HDR_LAYER)) { extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData()); } el = el.nextSiblingElement(); } bool result = (extraInfoLayers == exrLayerNames); if (!result) { dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!"; dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size()); std::set::const_iterator it1 = extraInfoLayers.begin(); std::set::const_iterator it2 = exrLayerNames.begin(); std::set::const_iterator end1 = extraInfoLayers.end(); for (; it1 != end1; ++it1, ++it2) { dbgKrita << it1->c_str() << it2->c_str(); } } return result; } KisImportExportErrorCode EXRConverter::decode(const QString &filename) { try { Imf::InputFile file(QFile::encodeName(filename)); Imath::Box2i dw = file.header().dataWindow(); Imath::Box2i displayWindow = file.header().displayWindow(); int width = dw.max.x - dw.min.x + 1; int height = dw.max.y - dw.min.y + 1; int dx = dw.min.x; int dy = dw.min.y; // Display the attributes of a file for (Imf::Header::ConstIterator it = file.header().begin(); it != file.header().end(); ++it) { dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName(); } // fetch Krita's extra layer info, which might have been stored previously QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header()); // Construct the list of LayerInfo QList informationObjects; QList groups; ImageType imageType = IT_UNKNOWN; const Imf::ChannelList &channels = file.header().channels(); std::set layerNames; channels.layers(layerNames); if (!extraLayersInfo.isNull() && !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) { // it is inconsistent anyway extraLayersInfo = QDomDocument(); } // Check if there are A, R, G, B channels dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:"; ExrPaintLayerInfo info; bool topLevelRGBFound = false; info.name = HDR_LAYER; QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B" << ".A" << ".R" << ".G" << ".B" << "A." << "R." << "G." << "B." << "A." << "R." << "G." << "B." << ".alpha" << ".red" << ".green" << ".blue"; for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { const Imf::Channel &channel = i.channel(); dbgFile << "Channel name = " << i.name() << " type = " << channel.type; QString qname = i.name(); if (topLevelChannelNames.contains(qname)) { topLevelRGBFound = true; dbgFile << "Found top-level channel" << qname; info.channelMap[qname] = qname; info.updateImageType(imfTypeToKisType(channel.type)); } // Channel names that don't contain a "." or that contain a // "." only at the beginning or at the end are not considered // to be part of any layer. else if (!qname.contains('.') || !qname.mid(1).contains('.') || !qname.left(qname.size() - 1).contains('.')) { warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel."; } } if (topLevelRGBFound) { dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType; informationObjects.push_back(info); imageType = info.imageType; } dbgFile << "Extra layers:" << layerNames.size(); for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) { info = ExrPaintLayerInfo(); dbgFile << "layer name = " << i->c_str(); info.name = i->c_str(); Imf::ChannelList::ConstIterator layerBegin, layerEnd; channels.channelsInLayer(*i, layerBegin, layerEnd); for (Imf::ChannelList::ConstIterator j = layerBegin; j != layerEnd; ++j) { const Imf::Channel &channel = j.channel(); info.updateImageType(imfTypeToKisType(channel.type)); QString qname = j.name(); QStringList list = qname.split('.'); QString layersuffix = list.last(); dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type; // Nuke writes the channels for sublayers as .red instead of .R, so convert those. // See https://bugs.kde.org/show_bug.cgi?id=393771 if (topLevelChannelNames.contains("." + layersuffix)) { layersuffix = layersuffix.at(0).toUpper(); } dbgFile << "\t\tsuffix" << layersuffix; if (list.size() > 1) { info.name = list[list.size()-2]; info.parent = searchGroup(&groups, list, 0, list.size() - 3); } info.channelMap[layersuffix] = qname; } if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) { informationObjects.push_back(info); if (imageType < info.imageType) { imageType = info.imageType; } } } dbgFile << "File has" << informationObjects.size() << "layer(s)"; // Set the colorspaces for (int i = 0; i < informationObjects.size(); ++i) { ExrPaintLayerInfo& info = informationObjects[i]; QString modelId; if (info.channelMap.size() == 1) { modelId = GrayAColorModelID.id(); QString key = info.channelMap.begin().key(); if (key != "G") { info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G")); QString channel = info.channelMap.begin().value(); info.channelMap.clear(); info.channelMap["G"] = channel; } } else if (info.channelMap.size() == 2) { modelId = GrayAColorModelID.id(); QMap::const_iterator it = info.channelMap.constBegin(); QMap::const_iterator end = info.channelMap.constEnd(); QString failingChannelKey; for (; it != end; ++it) { if (it.key() != "G" && it.key() != "A") { failingChannelKey = it.key(); break; } } info.remappedChannels.push_back( ExrPaintLayerInfo::Remap(failingChannelKey, "G")); QString failingChannelValue = info.channelMap[failingChannelKey]; info.channelMap.remove(failingChannelKey); info.channelMap["G"] = failingChannelValue; } else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) { if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) { modelId = RGBAColorModelID.id(); } else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) { modelId = XYZAColorModelID.id(); QMap newChannelMap; if (info.channelMap.contains("W")) { newChannelMap["A"] = info.channelMap["W"]; info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A")); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X")); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y")); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z")); } else if (info.channelMap.contains("A")) { newChannelMap["A"] = info.channelMap["A"]; } // The decode function expect R, G, B in the channel map newChannelMap["B"] = info.channelMap["X"]; newChannelMap["G"] = info.channelMap["Y"]; newChannelMap["R"] = info.channelMap["Z"]; info.channelMap = newChannelMap; } else { modelId = RGBAColorModelID.id(); QMap newChannelMap; QMap::iterator it = info.channelMap.begin(); newChannelMap["R"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R")); ++it; newChannelMap["G"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G")); ++it; newChannelMap["B"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B")); if (info.channelMap.size() == 4) { ++it; newChannelMap["A"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A")); } info.channelMap = newChannelMap; } } else { dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do."; } if (!modelId.isEmpty()) { info.colorSpace = kisTypeToColorSpace(modelId, info.imageType); } } // Get colorspace dbgFile << "Image type = " << imageType; const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType); if (!colorSpace) return ImportExportCodes::FormatColorSpaceUnsupported; dbgFile << "Colorspace: " << colorSpace->name(); // Set the colorspace on all groups for (int i = 0; i < groups.size(); ++i) { ExrGroupLayerInfo& info = groups[i]; info.colorSpace = colorSpace; } // Create the image // Make sure the created image is the same size as the displayWindow since // the dataWindow can be cropped in some cases. int displayWidth = displayWindow.max.x - displayWindow.min.x + 1; int displayHeight = displayWindow.max.y - displayWindow.min.y + 1; d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, ""); if (!d->image) { return ImportExportCodes::Failure; } /** * EXR semi-transparent images are expected to be rendered on * black to ensure correctness of the light model */ d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace)); // Create group layers for (int i = 0; i < groups.size(); ++i) { ExrGroupLayerInfo& info = groups[i]; Q_ASSERT(info.parent == 0 || info.parent->groupLayer); KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8); d->image->addNode(info.groupLayer, groupLayerParent); } // Load the layers for (int i = informationObjects.size() - 1; i >= 0; --i) { ExrPaintLayerInfo& info = informationObjects[i]; if (info.colorSpace) { dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id(); KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace); if (!layer) { return ImportExportCodes::Failure; } layer->setCompositeOpId(COMPOSITE_OVER); switch (info.channelMap.size()) { case 1: case 2: // Decode the data switch (info.imageType) { case IT_FLOAT16: d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF); break; case IT_FLOAT32: d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT); break; case IT_UNKNOWN: case IT_UNSUPPORTED: qFatal("Impossible error"); } break; case 3: case 4: // Decode the data switch (info.imageType) { case IT_FLOAT16: d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF); break; case IT_FLOAT32: d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT); break; case IT_UNKNOWN: case IT_UNSUPPORTED: qFatal("Impossible error"); } break; default: qFatal("Invalid number of channels: %i", info.channelMap.size()); } // Check if should set the channels if (!info.remappedChannels.isEmpty()) { QList values; Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) { QMap map; map["original"] = KisMetaData::Value(remap.original); map["current"] = KisMetaData::Value(remap.current); values.append(map); } layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values)); } // Add the layer KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); d->image->addNode(layer, groupLayerParent); } else { dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space"; } } // Set projectionColor to opaque d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace)); // After reading the image, notify the user about changed alpha. if (d->alphaWasModified) { QString msg = i18nc("@info", "The image contains pixels with zero alpha channel and non-zero " "color channels. Krita has modified those pixels to have " "at least some alpha. The initial values will not " "be reverted on saving the image back." "

" "This will hardly make any visual difference just keep it in mind."); if (d->showNotifications) { QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg); } else { warnKrita << "WARNING:" << msg; } } if (!extraLayersInfo.isNull()) { KisExrLayersSorter sorter(extraLayersInfo, d->image); } return ImportExportCodes::OK; } catch (std::exception &e) { dbgFile << "Error while reading from the exr file: " << e.what(); if (!KisImportExportAdditionalChecks::doesFileExist(filename)) { return ImportExportCodes::FileNotExist; } else if(!KisImportExportAdditionalChecks::isFileReadable(filename)) { return ImportExportCodes::NoAccessToRead; } else { return ImportExportCodes::ErrorWhileReading; } } return ImportExportCodes::OK; } KisImportExportErrorCode EXRConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP EXRConverter::image() { return d->image; } QString EXRConverter::errorMessage() const { return d->errorMessage; } template struct ExrPixel_ { _T_ data[size]; }; class Encoder { public: virtual ~Encoder() {} virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0; virtual void encodeData(int line) = 0; }; template class EncoderImpl : public Encoder { public: EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {} ~EncoderImpl() override {} void prepareFrameBuffer(Imf::FrameBuffer*, int line) override; void encodeData(int line) override; private: typedef ExrPixel_<_T_, size> ExrPixel; Imf::OutputFile* file; const ExrPaintLayerSaveInfo* info; QVector pixels; int m_width; }; template void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line) { int xstart = 0; int ystart = 0; ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width; for (int k = 0; k < size; ++k) { frameBuffer->insert(info->channels[k].toUtf8(), Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k], sizeof(ExrPixel) * 1, sizeof(ExrPixel) * m_width)); } } template void EncoderImpl<_T_, size, alphaPos>::encodeData(int line) { ExrPixel *rgba = pixels.data(); KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width); do { const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData()); for (int i = 0; i < size; ++i) { rgba->data[i] = dst[i]; } if (alphaPos != -1) { multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba); } ++rgba; } while (it->nextPixel()); } Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width) { dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount(); switch (info.layerDevice->colorSpace()->channelCount()) { case 1: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl < half, 1, -1 > (&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl < float, 1, -1 > (&file, &info, width); } break; } case 2: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } case 4: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } default: qFatal("Impossible error"); } return 0; } void encodeData(Imf::OutputFile& file, const QList& informationObjects, int width, int height) { QList encoders; Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { encoders.push_back(encoder(file, info, width)); } for (int y = 0; y < height; ++y) { Imf::FrameBuffer frameBuffer; Q_FOREACH (Encoder* encoder, encoders) { encoder->prepareFrameBuffer(&frameBuffer, y); } file.setFrameBuffer(frameBuffer); Q_FOREACH (Encoder* encoder, encoders) { encoder->encodeData(y); } file.writePixels(1); } qDeleteAll(encoders); } KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device) { const KoColorSpace *cs = device->colorSpace(); if (cs->colorDepthId() != Float16BitsColorDepthID && cs->colorDepthId() != Float32BitsColorDepthID) { cs = KoColorSpaceRegistry::instance()->colorSpace( cs->colorModelId() == GrayAColorModelID ? GrayAColorModelID.id() : RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (cs->colorModelId() != GrayColorModelID && cs->colorModelId() != RGBAColorModelID) { cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), cs->colorDepthId().id()); } if (*cs != *device->colorSpace()) { device = new KisPaintDevice(*device); device->convertTo(cs); } return device; } KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer) { KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = layer->image(); KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); // Make the header qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); ExrPaintLayerSaveInfo info; info.layer = layer; info.layerDevice = wrapLayerDevice(layer->paintDevice()); Imf::PixelType pixelType = Imf::NUM_PIXELTYPES; if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { pixelType = Imf::HALF; } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { pixelType = Imf::FLOAT; } header.channels().insert("R", Imf::Channel(pixelType)); header.channels().insert("G", Imf::Channel(pixelType)); header.channels().insert("B", Imf::Channel(pixelType)); header.channels().insert("A", Imf::Channel(pixelType)); info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); info.pixelType = pixelType; // Open file for writing try { Imf::OutputFile file(QFile::encodeName(filename), header); QList informationObjects; informationObjects.push_back(info); encodeData(file, informationObjects, width, height); return ImportExportCodes::OK; } catch(std::exception &e) { dbgFile << "Exception while writing to exr file: " << e.what(); if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) { return ImportExportCodes::NoAccessToWrite; } return ImportExportCodes::Failure; } } QString remap(const QMap& current2original, const QString& current) { if (current2original.contains(current)) { return current2original[current]; } return current; } void EXRConverter::Private::makeLayerNamesUnique(QList& informationObjects) { typedef QMultiMap::iterator> NamesMap; NamesMap namesMap; { QList::iterator it = informationObjects.begin(); QList::iterator end = informationObjects.end(); for (; it != end; ++it) { namesMap.insert(it->name, it); } } Q_FOREACH (const QString &key, namesMap.keys()) { if (namesMap.count(key) > 1) { KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; } QString strippedName = key.left(key.size() - 1); // trim the ending dot int nameCounter = 0; NamesMap::iterator it = namesMap.find(key); NamesMap::iterator end = namesMap.end(); for (; it != end; ++it) { QString newName = QString("%1_%2.") .arg(strippedName) .arg(nameCounter++); it.value()->name = newName; QList::iterator channelsIt = it.value()->channels.begin(); QList::iterator channelsEnd = it.value()->channels.end(); for (; channelsIt != channelsEnd; ++channelsIt) { channelsIt->replace(key, newName); } } } } } void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent) { QSet layersNotSaved; for (uint i = 0; i < parent->childCount(); ++i) { KisNodeSP node = parent->at(i); if (KisPaintLayerSP paintLayer = dynamic_cast(node.data())) { QMap current2original; if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) { const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap"); QList< KisMetaData::Value> values = entry.value().asArray(); Q_FOREACH (const KisMetaData::Value& value, values) { QMap map = value.asStructure(); if (map.contains("original") && map.contains("current")) { current2original[map["current"].toString()] = map["original"].toString(); } } } ExrPaintLayerSaveInfo info; info.name = name + paintLayer->name() + '.'; info.layer = paintLayer; info.layerDevice = wrapLayerDevice(paintLayer->paintDevice()); if (info.name == QString(HDR_LAYER) + ".") { info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); } else { if (paintLayer->colorSpace()->colorModelId() == RGBAColorModelID) { info.channels.push_back(info.name + remap(current2original, "R")); info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "B")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayAColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); } else if (paintLayer->colorSpace()->colorModelId() == XYZAColorModelID) { info.channels.push_back(info.name + remap(current2original, "X")); info.channels.push_back(info.name + remap(current2original, "Y")); info.channels.push_back(info.name + remap(current2original, "Z")); info.channels.push_back(info.name + remap(current2original, "A")); } } if (paintLayer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { info.pixelType = Imf::HALF; } else if (paintLayer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { info.pixelType = Imf::FLOAT; } else { info.pixelType = Imf::NUM_PIXELTYPES; } if (info.pixelType < Imf::NUM_PIXELTYPES) { dbgFile << "Going to save layer" << info.name; informationObjects.push_back(info); } else { warnFile << "Will not save layer" << info.name; layersNotSaved << node; } } else if (KisGroupLayerSP groupLayer = dynamic_cast(node.data())) { recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer); } else { /** * The EXR can store paint and group layers only. The rest will * go to /dev/null :( */ layersNotSaved.insert(node); } } if (!layersNotSaved.isEmpty()) { reportLayersNotSaved(layersNotSaved); } } void EXRConverter::Private::reportLayersNotSaved(const QSet &layersNotSaved) { QString layersList; QTextStream textStream(&layersList); textStream.setCodec("UTF-8"); Q_FOREACH (KisNodeSP node, layersNotSaved) { textStream << "
  • " << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "
  • "; } QString msg = i18nc("@info", "

    The following layers have a type that is not supported by EXR format:

    " "
      %1

    " "

    these layers have not been saved to the final EXR file

    ", layersList); errorMessage = msg; } QString EXRConverter::Private::fetchExtraLayersInfo(QList& informationObjects) { KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty()); if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") { return QString(); } QDomDocument doc("krita-extra-layers-info"); doc.appendChild(doc.createElement("root")); QDomElement rootElement = doc.documentElement(); for (int i = 0; i < informationObjects.size(); i++) { ExrPaintLayerSaveInfo &info = informationObjects[i]; quint32 unused; KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false); QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc); - // cut the ending '.' QString strippedName = info.name.left(info.name.size() - 1); - KIS_SAFE_ASSERT_RECOVER_NOOP(info.layer->name() == strippedName); el.setAttribute(EXR_NAME, strippedName); rootElement.appendChild(el); } return doc.toString(); } KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten) { KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = layer->image(); KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); if (flatten) { KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); return buildFile(filename, l); } else { QList informationObjects; d->recBuildPaintLayerSaveInfo(informationObjects, "", layer); if(informationObjects.isEmpty()) { return ImportExportCodes::FormatColorSpaceUnsupported; } d->makeLayerNamesUnique(informationObjects); QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8(); if (!extraLayersInfo.isNull()) { header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData())); } dbgFile << informationObjects.size() << " layers to save"; Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { if (info.pixelType < Imf::NUM_PIXELTYPES) { Q_FOREACH (const QString& channel, info.channels) { dbgFile << channel << " " << info.pixelType; header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType)); } } } // Open file for writing try { Imf::OutputFile file(QFile::encodeName(filename), header); encodeData(file, informationObjects, width, height); return ImportExportCodes::OK; } catch(std::exception &e) { dbgFile << "Exception while writing to exr file: " << e.what(); if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) { return ImportExportCodes::NoAccessToWrite; } return ImportExportCodes::ErrorWhileWriting; } } } void EXRConverter::cancel() { warnKrita << "WARNING: Cancelling of an EXR loading is not supported!"; } 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/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/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/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/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(); } }