diff --git a/CMakeLists.txt b/CMakeLists.txt index c653572fca..4ade41657d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,748 +1,751 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.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(-Werror=delete-incomplete) + 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.2.0-pre-alpha") # 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 2) # 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("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(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 Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # 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 ) 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(KF5KIO ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5KIO_FOUND HAVE_KIO) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE's KIO Framework" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used for recent document handling") 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_KRITADEVS "-O3 -g" CACHE STRING "" FORCE) 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) # 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.18.0") 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(Exiv2 0.16 REQUIRED) set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image metadata library and tools" URL "http://www.exiv2.org" PURPOSE "Required by Krita") ## ## 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 "-Wabi -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -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.") ############################ ############################# ## 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) add_subdirectory(benchmarks) 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/KoConfig.h.cmake b/KoConfig.h.cmake index fbf063d758..783c2d57f6 100644 --- a/KoConfig.h.cmake +++ b/KoConfig.h.cmake @@ -1,62 +1,65 @@ // Check windows #ifdef Q_OS_WIN #ifdef _WIN64 #define ENV64BIT #else #define ENV32BIT #endif #endif // Check GCC #if __GNUC__ #if defined (__x86_64__) || defined (__ppc64__) #define ENV64BIT #else #define ENV32BIT #endif #endif #ifdef __APPLE__ # ifdef __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # else # undef WORDS_BIGENDIAN # endif #else /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ #cmakedefine WORDS_BIGENDIAN ${CMAKE_WORDS_BIGENDIAN} #endif /* Defines if the Dr. Mingw crash handler should be used */ #cmakedefine USE_DRMINGW 1 /* Number of bits in a file offset, on hosts where this is settable. */ #define _FILE_OFFSET_BITS 64 /* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ /* #undef _LARGEFILE_SOURCE */ /* Define for large files, on AIX-style hosts. */ /* #undef _LARGE_FILES */ /* Defines if your system has the OpenEXR library */ #cmakedefine HAVE_OPENEXR 1 /* Defines if we use lcms2 */ #cmakedefine HAVE_LCMS2 1 /* Defines if we use lcms2.4 */ #cmakedefine HAVE_LCMS24 1 /* Defines if KIO is present */ #cmakedefine HAVE_KIO 1 /* Defines if DBUS is present */ #cmakedefine HAVE_DBUS 1 /* Defines if KCrash is present */ #cmakedefine HAVE_KCRASH 1 /* This variable contains the path to the current build directory */ #define KRITA_BUILD_DIR "${CMAKE_BINARY_DIR}" + +/* This variable contains the path to the data install dir */ +#define KRITA_EXTRA_RESOURCE_DIRS "${CMAKE_INSTALL_PREFIX}/${DATA_INSTALL_DIR}:${CMAKE_SOURCE_DIR}/krita/data" diff --git a/benchmarks/kis_painter_benchmark.cpp b/benchmarks/kis_painter_benchmark.cpp index c604fbdeb4..0d12c2718e 100644 --- a/benchmarks/kis_painter_benchmark.cpp +++ b/benchmarks/kis_painter_benchmark.cpp @@ -1,278 +1,465 @@ /* * Copyright (c) 2010 Lukáš Tvrdý lukast.dev@gmail.com * * 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. */ #if defined(_WIN32) || defined(_WIN64) #include #define srand48 srand inline double drand48() { return double(rand()) / RAND_MAX; } #endif #include #include #include #include "kis_painter_benchmark.h" #include "kis_benchmark_values.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include #include #include #include #include #include #include +#include "kis_paintop_utils.h" +#include "kis_algebra_2d.h" +#include "kis_paint_device_debug_utils.h" +#include "KisRenderedDab.h" + #define SAVE_OUTPUT #define CYCLES 20 static const int LINE_COUNT = 100; static const int LINE_WIDTH = 1; void KisPainterBenchmark::initTestCase() { m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); m_color = KoColor(m_colorSpace); m_color.fromQColor(Qt::red); srand48(0); for (int i = 0; i < LINE_COUNT ;i++){ m_points.append( QPointF(drand48() * TEST_IMAGE_WIDTH, drand48() * TEST_IMAGE_HEIGHT) ); m_points.append( QPointF(drand48() * TEST_IMAGE_WIDTH, drand48() * TEST_IMAGE_HEIGHT) ); } } void KisPainterBenchmark::cleanupTestCase() { } void KisPainterBenchmark::benchmarkBitBlt() { KisPaintDeviceSP src = new KisPaintDevice(m_colorSpace); KisPaintDeviceSP dst = new KisPaintDevice(m_colorSpace); src->fill(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT, m_color.data()); dst->fill(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT, m_color.data()); KisPainter gc(dst); QPoint pos(0,0); QRect rc(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT); QBENCHMARK{ for (int i = 0; i < CYCLES ; i++){ gc.bitBlt(pos,src,rc); } } } void KisPainterBenchmark::benchmarkFastBitBlt() { KisPaintDeviceSP src = new KisPaintDevice(m_colorSpace); KisPaintDeviceSP dst = new KisPaintDevice(m_colorSpace); src->fill(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT, m_color.data()); dst->fill(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT, m_color.data()); KisPainter gc(dst); gc.setCompositeOp(m_colorSpace->compositeOp(COMPOSITE_COPY)); QPoint pos(0,0); QRect rc(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT); QBENCHMARK{ for (int i = 0; i < CYCLES ; i++){ gc.bitBlt(pos,src,rc); } } } void KisPainterBenchmark::benchmarkBitBltSelection() { KisPaintDeviceSP src = new KisPaintDevice(m_colorSpace); KisPaintDeviceSP dst = new KisPaintDevice(m_colorSpace); src->fill(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT, m_color.data()); dst->fill(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT, m_color.data()); KisSelectionSP selection = new KisSelection(); selection->pixelSelection()->select(QRect(0, 0, TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT)); selection->updateProjection(); KisPainter gc(dst); gc.setSelection(selection); QPoint pos(0,0); QRect rc(0,0,TEST_IMAGE_WIDTH, TEST_IMAGE_HEIGHT); QBENCHMARK{ for (int i = 0; i < CYCLES ; i++){ gc.bitBlt(pos,src,rc); } } } void KisPainterBenchmark::benchmarkFixedBitBlt() { QImage img(TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT,QImage::Format_ARGB32); img.fill(255); KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(m_colorSpace); fdev->convertFromQImage(img, 0); KisPaintDeviceSP dst = new KisPaintDevice(m_colorSpace); KisPainter gc(dst); QPoint pos(0, 0); QRect rc = img.rect(); QBENCHMARK{ for (int i = 0; i < CYCLES ; i++){ gc.bltFixed(pos,fdev,rc); } } } void KisPainterBenchmark::benchmarkFixedBitBltSelection() { QImage img(TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT,QImage::Format_ARGB32); img.fill(128); KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(m_colorSpace); fdev->convertFromQImage(img, 0); KisPaintDeviceSP dst = new KisPaintDevice(m_colorSpace); KisSelectionSP selection = new KisSelection(); selection->pixelSelection()->select(QRect(0, 0, TEST_IMAGE_WIDTH , TEST_IMAGE_HEIGHT)); selection->updateProjection(); KisPainter gc(dst); gc.setSelection(selection); QPoint pos(0, 0); QRect rc = img.rect(); QBENCHMARK{ for (int i = 0; i < CYCLES ; i++){ gc.bltFixed(pos,fdev,rc); } } } void KisPainterBenchmark::benchmarkDrawThickLine() { KisPaintDeviceSP dev = new KisPaintDevice(m_colorSpace); KoColor color(m_colorSpace); color.fromQColor(Qt::white); dev->clear(); dev->fill(0,0,TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT,color.data()); color.fromQColor(Qt::black); KisPainter painter(dev); painter.setPaintColor(color); QBENCHMARK{ for (int i = 0; i < LINE_COUNT; i++){ painter.drawThickLine(m_points[i*2],m_points[i*2+1],LINE_WIDTH,LINE_WIDTH); } } #ifdef SAVE_OUTPUT dev->convertToQImage(m_colorSpace->profile()).save("drawThickLine.png"); #endif } void KisPainterBenchmark::benchmarkDrawQtLine() { KisPaintDeviceSP dev = new KisPaintDevice(m_colorSpace); KoColor color(m_colorSpace); color.fromQColor(Qt::white); dev->clear(); dev->fill(0,0,TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT,color.data()); color.fromQColor(Qt::black); KisPainter painter(dev); painter.setPaintColor(color); painter.setFillStyle(KisPainter::FillStyleForegroundColor); QPen pen; pen.setWidth(LINE_WIDTH); pen.setColor(Qt::white); pen.setCapStyle(Qt::RoundCap); QBENCHMARK{ for (int i = 0; i < LINE_COUNT; i++){ QPainterPath path; path.moveTo(m_points[i*2]); path.lineTo(m_points[i*2 + 1]); painter.drawPainterPath(path, pen); } } #ifdef SAVE_OUTPUT dev->convertToQImage(m_colorSpace->profile(),0,0,TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT).save("drawQtLine.png"); #endif } void KisPainterBenchmark::benchmarkDrawScanLine() { KisPaintDeviceSP dev = new KisPaintDevice(m_colorSpace); KoColor color(m_colorSpace); color.fromQColor(Qt::white); dev->clear(); dev->fill(0,0,TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT,color.data()); color.fromQColor(Qt::black); KisPainter painter(dev); painter.setPaintColor(color); painter.setFillStyle(KisPainter::FillStyleForegroundColor); QBENCHMARK{ for (int i = 0; i < LINE_COUNT; i++){ painter.drawLine(m_points[i*2],m_points[i*2+1],LINE_WIDTH,true); } } #ifdef SAVE_OUTPUT dev->convertToQImage(m_colorSpace->profile(),0,0,TEST_IMAGE_WIDTH,TEST_IMAGE_HEIGHT).save("drawScanLine.png"); #endif } +void KisPainterBenchmark::benchmarkBitBlt2() +{ + quint8 p = 128; + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); + + KisPaintDeviceSP src = new KisPaintDevice(cs); + KisPaintDeviceSP dst = new KisPaintDevice(cs); + + KoColor color(&p, cs); + QRect fillRect(0,0,5000,5000); + + src->fill(fillRect, color); + + QBENCHMARK { + KisPainter gc(dst); + gc.bitBlt(QPoint(), src, fillRect); + } +} + +void KisPainterBenchmark::benchmarkBitBltOldData() +{ + quint8 p = 128; + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); + + KisPaintDeviceSP src = new KisPaintDevice(cs); + KisPaintDeviceSP dst = new KisPaintDevice(cs); + + KoColor color(&p, cs); + QRect fillRect(0,0,5000,5000); + + src->fill(fillRect, color); + + QBENCHMARK { + KisPainter gc(dst); + gc.bitBltOldData(QPoint(), src, fillRect); + } +} + + +void benchmarkMassiveBltFixedImpl(int numDabs, int size, qreal spacing, int idealNumPatches, Qt::Orientations direction) +{ + const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); + KisPaintDeviceSP dst = new KisPaintDevice(cs); + + QList colors; + colors << QColor(255, 0, 0, 200); + colors << QColor(0, 255, 0, 200); + colors << QColor(0, 0, 255, 200); + + QRect devicesRect; + QList devices; + + const int step = spacing * size; + + for (int i = 0; i < numDabs; i++) { + const QRect rc = + direction == Qt::Horizontal ? QRect(10 + i * step, 0, size, size) : + direction == Qt::Vertical ? QRect(0, 10 + i * step, size, size) : + QRect(10 + i * step, 10 + i * step, size, size); + + KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs); + dev->setRect(rc); + dev->initialize(); + dev->fill(rc, KoColor(colors[i % 3], cs)); + dev->fill(kisGrowRect(rc, -5), KoColor(Qt::white, cs)); + + KisRenderedDab dab; + dab.device = dev; + dab.offset = dev->bounds().topLeft(); + dab.opacity = 1.0; + dab.flow = 1.0; + + devices << dab; + devicesRect |= rc; + } + + const QRect fullRect = kisGrowRect(devicesRect, 10); + + { + KisPainter painter(dst); + painter.bltFixed(fullRect, devices); + painter.end(); + //QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect), + // "kispainter_test", + // "massive_bitblt_benchmark", + // "initial")); + dst->clear(); + } + + + QVector dabRects; + Q_FOREACH (const KisRenderedDab &dab, devices) { + dabRects.append(dab.realBounds()); + } + + QElapsedTimer t; + + qint64 massiveTime = 0; + int massiveTries = 0; + int numRects = 0; + int avgPatchSize = 0; + + for (int i = 0; i < 50 || massiveTime > 5000000; i++) { + QVector rects = KisPaintOpUtils::splitDabsIntoRects(dabRects, idealNumPatches, size, spacing); + numRects = rects.size(); + + // HACK: please calculate real *average*! + avgPatchSize = KisAlgebra2D::maxDimension(rects.first()); + + t.start(); + + KisPainter painter(dst); + Q_FOREACH (const QRect &rc, rects) { + painter.bltFixed(rc, devices); + } + painter.end(); + + massiveTime += t.nsecsElapsed() / 1000; + massiveTries++; + dst->clear(); + } + + qint64 linearTime = 0; + int linearTries = 0; + + for (int i = 0; i < 50 || linearTime > 5000000; i++) { + t.start(); + + KisPainter painter(dst); + Q_FOREACH (const KisRenderedDab &dab, devices) { + painter.setOpacity(255 * dab.opacity); + painter.setFlow(255 * dab.flow); + painter.bltFixed(dab.offset, dab.device, dab.device->bounds()); + } + painter.end(); + + linearTime += t.nsecsElapsed() / 1000; + linearTries++; + dst->clear(); + } + + const qreal avgMassive = qreal(massiveTime) / massiveTries; + const qreal avgLinear = qreal(linearTime) / linearTries; + + const QString directionMark = + direction == Qt::Horizontal ? "H" : + direction == Qt::Vertical ? "V" : "D"; + + qDebug() + << "D:" << size + << "S:" << spacing + << "N:" << numDabs + << "P (px):" << avgPatchSize + << "R:" << numRects + << "Dir:" << directionMark + << "\t" + << qPrintable(QString("Massive (usec): %1").arg(QString::number(avgMassive, 'f', 2), 8)) + << "\t" + << qPrintable(QString("Linear (usec): %1").arg(QString::number(avgLinear, 'f', 2), 8)) + << (avgMassive < avgLinear ? "*" : " ") + << qPrintable(QString("%1") + .arg(QString::number((avgMassive - avgLinear) / avgLinear * 100.0, 'f', 2), 8)) + << qRound(size + size * spacing * (numDabs - 1)); +} + + +void KisPainterBenchmark::benchmarkMassiveBltFixed() +{ + const qreal sp = 0.14; + const int idealThreadCount = 8; + + for (int d = 50; d < 301; d += 50) { + for (int n = 1; n < 150; n = qCeil(n * 1.5)) { + benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Horizontal); + benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Vertical); + benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Vertical | Qt::Horizontal); + } + } +} + + + QTEST_MAIN(KisPainterBenchmark) diff --git a/benchmarks/kis_painter_benchmark.h b/benchmarks/kis_painter_benchmark.h index 155a1d7d07..f426c8e9e1 100644 --- a/benchmarks/kis_painter_benchmark.h +++ b/benchmarks/kis_painter_benchmark.h @@ -1,54 +1,59 @@ /* * Copyright (c) 2010 Lukáš Tvrdý lukast.dev@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTER_BENCHMARK_H #define KIS_PAINTER_BENCHMARK_H #include #include #include class KisPainterBenchmark : public QObject { Q_OBJECT private: const KoColorSpace * m_colorSpace; KoColor m_color; QVector m_points; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void benchmarkBitBlt(); void benchmarkFastBitBlt(); void benchmarkBitBltSelection(); void benchmarkFixedBitBlt(); void benchmarkFixedBitBltSelection(); void benchmarkDrawThickLine(); void benchmarkDrawQtLine(); void benchmarkDrawScanLine(); + + void benchmarkBitBlt2(); + void benchmarkBitBltOldData(); + void benchmarkMassiveBltFixed(); + }; #endif diff --git a/krita/data/CMakeLists.txt b/krita/data/CMakeLists.txt index b1b313b14e..50e35120da 100644 --- a/krita/data/CMakeLists.txt +++ b/krita/data/CMakeLists.txt @@ -1,24 +1,25 @@ add_subdirectory( actions ) add_subdirectory( brushes ) add_subdirectory( bundles ) add_subdirectory( patterns ) add_subdirectory( gradients ) add_subdirectory( profiles ) add_subdirectory( templates ) add_subdirectory( workspaces ) add_subdirectory( themes ) add_subdirectory( predefined_image_sizes ) add_subdirectory( input ) add_subdirectory( shortcuts ) add_subdirectory( paintoppresets ) add_subdirectory( palettes ) add_subdirectory( symbols ) add_subdirectory( preset_icons ) +add_subdirectory( metadata/schemas ) ########### install files ############### install( FILES kritarc DESTINATION ${CONFIG_INSTALL_DIR} ) diff --git a/krita/data/metadata/schemas/CMakeLists.txt b/krita/data/metadata/schemas/CMakeLists.txt new file mode 100644 index 0000000000..29dd66cf51 --- /dev/null +++ b/krita/data/metadata/schemas/CMakeLists.txt @@ -0,0 +1,12 @@ +########### install schemas ############# +install( FILES + dc.schema + exif.schema + tiff.schema + mkn.schema + xmp.schema + xmpmm.schema + xmprights.schema + + DESTINATION ${DATA_INSTALL_DIR}/krita/metadata/schemas) + diff --git a/libs/image/metadata/schemas/dc.schema b/krita/data/metadata/schemas/dc.schema similarity index 100% rename from libs/image/metadata/schemas/dc.schema rename to krita/data/metadata/schemas/dc.schema diff --git a/libs/image/metadata/schemas/exif.schema b/krita/data/metadata/schemas/exif.schema similarity index 100% rename from libs/image/metadata/schemas/exif.schema rename to krita/data/metadata/schemas/exif.schema diff --git a/libs/image/metadata/schemas/mkn.schema b/krita/data/metadata/schemas/mkn.schema similarity index 100% rename from libs/image/metadata/schemas/mkn.schema rename to krita/data/metadata/schemas/mkn.schema diff --git a/libs/image/metadata/schemas/tiff.schema b/krita/data/metadata/schemas/tiff.schema similarity index 100% rename from libs/image/metadata/schemas/tiff.schema rename to krita/data/metadata/schemas/tiff.schema diff --git a/libs/image/metadata/schemas/xmp.schema b/krita/data/metadata/schemas/xmp.schema similarity index 100% rename from libs/image/metadata/schemas/xmp.schema rename to krita/data/metadata/schemas/xmp.schema diff --git a/libs/image/metadata/schemas/xmpmm.schema b/krita/data/metadata/schemas/xmpmm.schema similarity index 100% rename from libs/image/metadata/schemas/xmpmm.schema rename to krita/data/metadata/schemas/xmpmm.schema diff --git a/libs/image/metadata/schemas/xmprights.schema b/krita/data/metadata/schemas/xmprights.schema similarity index 100% rename from libs/image/metadata/schemas/xmprights.schema rename to krita/data/metadata/schemas/xmprights.schema diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index d0259a3782..d5eaed71bc 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,249 +1,250 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Fundación Krita + La Fondation Krita Fondazione Krita Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Фундація Krita xxKrita Foundationxx Krita 基金会 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

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

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

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

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

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

xxKrita is the full-featured digital art studio.xx

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png none none none none none none none none none none none none none none none none none none none none Graphics KDE krita
diff --git a/libs/flake/KoConnectionShape.cpp b/libs/flake/KoConnectionShape.cpp index c5eae83fc5..fa37c5e84c 100644 --- a/libs/flake/KoConnectionShape.cpp +++ b/libs/flake/KoConnectionShape.cpp @@ -1,780 +1,780 @@ /* This file is part of the KDE project * Copyright (C) 2007 Boudewijn Rempt * Copyright (C) 2007,2009 Thorsten Zachmann * Copyright (C) 2007,2009,2010 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoConnectionShape.h" #include "KoConnectionShape_p.h" #include "KoViewConverter.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoConnectionShapeLoadingUpdater.h" #include "KoPathShapeLoader.h" #include "KoPathPoint.h" #include "KoShapeBackground.h" #include #include #include #include #include #include KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q) : KoParameterShapePrivate(q), shape1(0), shape2(0), connectionPointId1(-1), connectionPointId2(-1), connectionType(KoConnectionShape::Standard), forceUpdate(false), hasCustomPath(false) { } KoConnectionShapePrivate::KoConnectionShapePrivate(const KoConnectionShapePrivate &rhs, KoConnectionShape *q) : KoParameterShapePrivate(rhs, q), path(rhs.path), shape1(0), // FIXME: it should point to the new shapes!!! shape2(0), // FIXME: it should point to the new shapes!!! connectionPointId1(rhs.connectionPointId1), connectionPointId2(rhs.connectionPointId2), connectionType(rhs.connectionType), forceUpdate(rhs.forceUpdate), hasCustomPath(rhs.hasCustomPath) { } QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const { Q_Q(const KoConnectionShape); QPointF direction; if (handleConnected(handleId)) { KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? shape1 : shape2; int connectionPointId = handleId == KoConnectionShape::StartHandle ? connectionPointId1 : connectionPointId2; KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection; if (ed == KoConnectionPoint::AllDirections) { QPointF handlePoint = q->shapeToDocument(handles[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center); /* * Determine the best escape direction from the position of the handle point * and the position and orientation of the attached shape. * The idea is to define 4 sectors, one for each edge of the attached shape. * Each sector starts at the center point of the attached shape and has it * left and right edge going through the two points which define the edge. * Then we check which sector contains our handle point, for which we can * simply calculate the corresponding direction which is orthogonal to the * corresponding bounding box edge. * From that we derive the escape direction from looking at the main coordinate * of the orthogonal direction. */ // define our edge points in the right order const KoFlake::AnchorPosition corners[4] = { KoFlake::BottomRight, KoFlake::BottomLeft, KoFlake::TopLeft, KoFlake::TopRight }; QPointF vHandle = handlePoint-centerPoint; for (int i = 0; i < 4; ++i) { // first point of bounding box edge QPointF p1 = attachedShape->absolutePosition(corners[i]); // second point of bounding box edge QPointF p2 = attachedShape->absolutePosition(corners[(i+1)%4]); // check on which side of the first sector edge our second sector edge is const qreal c0 = crossProd(p1-centerPoint, p2-centerPoint); // check on which side of the first sector edge our handle point is const qreal c1 = crossProd(p1-centerPoint, vHandle); // second edge and handle point must be on the same side of first edge if ((c0 < 0 && c1 > 0) || (c0 > 0 && c1 < 0)) continue; // check on which side of the handle point our second sector edge is const qreal c2 = crossProd(vHandle, p2-centerPoint); // second edge must be on the same side of the handle point as on first edge if ((c0 < 0 && c2 > 0) || (c0 > 0 && c2 < 0)) continue; // now we found the correct edge QPointF vDir = 0.5 *(p1+p2) - centerPoint; // look at coordinate with the greatest absolute value // and construct our escape direction accordingly const qreal xabs = qAbs(vDir.x()); const qreal yabs = qAbs(vDir.y()); if (xabs > yabs) { direction.rx() = vDir.x() > 0 ? 1.0 : -1.0; direction.ry() = 0.0; } else { direction.rx() = 0.0; direction.ry() = vDir.y() > 0 ? 1.0 : -1.0; } break; } } else if (ed == KoConnectionPoint::HorizontalDirections) { QPointF handlePoint = q->shapeToDocument(handles[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center); // use horizontal direction pointing away from center point if (handlePoint.x() < centerPoint.x()) direction = QPointF(-1.0, 0.0); else direction = QPointF(1.0, 0.0); } else if (ed == KoConnectionPoint::VerticalDirections) { QPointF handlePoint = q->shapeToDocument(handles[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center); // use vertical direction pointing away from center point if (handlePoint.y() < centerPoint.y()) direction = QPointF(0.0, -1.0); else direction = QPointF(0.0, 1.0); } else if (ed == KoConnectionPoint::LeftDirection) { direction = QPointF(-1.0, 0.0); } else if (ed == KoConnectionPoint::RightDirection) { direction = QPointF(1.0, 0.0); } else if (ed == KoConnectionPoint::UpDirection) { direction = QPointF(0.0, -1.0); } else if (ed == KoConnectionPoint::DownDirection) { direction = QPointF(0.0, 1.0); } // transform escape direction by using our own transformation matrix QTransform invMatrix = q->absoluteTransformation(0).inverted(); direction = invMatrix.map(direction) - invMatrix.map(QPointF()); direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); } return direction; } bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect) { 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 coincidient if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) { // vertical, coincident isect = 0.5 * (p1 + p2); } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) { // horizontal, coincident isect = 0.5 * (p1 + p2); } else { return false; } } else { // they are intersecting normally isect = p1 + sp1 * d1; } return true; } QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2) { QPointF perpendicular(d1.y(), -d1.x()); qreal sp = scalarProd(perpendicular, p2 - p1); if (sp < 0.0) perpendicular *= -1.0; return perpendicular; } void KoConnectionShapePrivate::normalPath(const qreal MinimumEscapeLength) { // Clear the path to build it again. path.clear(); 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 (handleConnected(KoConnectionShape::StartHandle) && handleConnected(KoConnectionShape::EndHandle)) { QPointF intersection; // TODO: check if this loop actually ever exits? (DK) while (true) { // first check if directions from current edge points intersect if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) { // directions intersect, we have another edge point and be done edges1.append(intersection); break; } // check if we are going toward the other handle qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1); if (sp >= 0.0) { // if we are having the same direction, go all the way toward // the other handle, else only go half the way if (direction1 == direction2) edgePoint1 += sp * direction1; else edgePoint1 += 0.5 * sp * direction1; edges1.append(edgePoint1); // switch direction direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); } else { // we are not going into the same direction, so switch direction direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); } } } path.append(edges1); path.append(edges2); path.append(handles[KoConnectionShape::EndHandle]); } qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.x() + v1.y() * v2.y(); } qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.y() - v1.y() * v2.x(); } bool KoConnectionShapePrivate::handleConnected(int handleId) const { if (handleId == KoConnectionShape::StartHandle && shape1 && connectionPointId1 >= 0) return true; if (handleId == KoConnectionShape::EndHandle && shape2 && connectionPointId2 >= 0) return true; return false; } void KoConnectionShape::updateConnections() { Q_D(KoConnectionShape); bool updateHandles = false; if (d->handleConnected(StartHandle)) { if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { // map connection point into our shape coordinates QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position)); if (d->handles[StartHandle] != p) { d->handles[StartHandle] = p; updateHandles = true; } } } if (d->handleConnected(EndHandle)) { if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { // map connection point into our shape coordinates QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position)); if (d->handles[EndHandle] != p) { d->handles[EndHandle] = p; updateHandles = true; } } } if (updateHandles || d->forceUpdate) { update(); // ugly, for repainting the connection we just changed updatePath(QSizeF()); update(); // ugly, for repainting the connection we just changed d->forceUpdate = false; } } KoConnectionShape::KoConnectionShape() : KoParameterShape(new KoConnectionShapePrivate(this)) { Q_D(KoConnectionShape); d->handles.push_back(QPointF(0, 0)); d->handles.push_back(QPointF(140, 140)); moveTo(d->handles[StartHandle]); lineTo(d->handles[EndHandle]); updatePath(QSizeF(140, 140)); clearConnectionPoints(); } KoConnectionShape::KoConnectionShape(const KoConnectionShape &rhs) : KoParameterShape(new KoConnectionShapePrivate(*rhs.d_func(), this)) { } KoConnectionShape::~KoConnectionShape() { Q_D(KoConnectionShape); if (d->shape1) d->shape1->removeDependee(this); if (d->shape2) d->shape2->removeDependee(this); } KoShape *KoConnectionShape::cloneShape() const { return new KoConnectionShape(*this); } void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const { Q_D(const KoConnectionShape); context.xmlWriter().startElement("draw:connector"); saveOdfAttributes(context, OdfMandatories | OdfAdditionalAttributes); switch (d->connectionType) { case Lines: context.xmlWriter().addAttribute("draw:type", "lines"); break; case Straight: context.xmlWriter().addAttribute("draw:type", "line"); break; case Curve: context.xmlWriter().addAttribute("draw:type", "curve"); break; default: context.xmlWriter().addAttribute("draw:type", "standard"); break; } if (d->shape1) { context.xmlWriter().addAttribute("draw:start-shape", context.xmlid(d->shape1, "shape", KoElementReference::Counter).toString()); context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointId1); } else { QPointF p(shapeToDocument(d->handles[StartHandle]) * context.shapeOffset(this)); - context.xmlWriter().addAttributePt("svg:x1", p.x()); - context.xmlWriter().addAttributePt("svg:y1", p.y()); + context.xmlWriter().addAttribute("svg:x1", p.x()); + context.xmlWriter().addAttribute("svg:y1", p.y()); } if (d->shape2) { context.xmlWriter().addAttribute("draw:end-shape", context.xmlid(d->shape2, "shape", KoElementReference::Counter).toString()); context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointId2); } else { QPointF p(shapeToDocument(d->handles[EndHandle]) * context.shapeOffset(this)); - context.xmlWriter().addAttributePt("svg:x2", p.x()); - context.xmlWriter().addAttributePt("svg:y2", p.y()); + context.xmlWriter().addAttribute("svg:x2", p.x()); + context.xmlWriter().addAttribute("svg:y2", p.y()); } // write the path data context.xmlWriter().addAttribute("svg:d", toString()); saveOdfAttributes(context, OdfViewbox); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoConnectionShape); loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes); QString type = element.attributeNS(KoXmlNS::draw, "type", "standard"); if (type == "lines") d->connectionType = Lines; else if (type == "line") d->connectionType = Straight; else if (type == "curve") d->connectionType = Curve; else d->connectionType = Standard; // reset connection point indices d->connectionPointId1 = -1; d->connectionPointId2 = -1; // reset connected shapes d->shape1 = 0; d->shape2 = 0; if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) { d->connectionPointId1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt(); QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString()); debugFlake << "references start-shape" << shapeId1 << "at glue-point" << d->connectionPointId1; d->shape1 = context.shapeById(shapeId1); if (d->shape1) { debugFlake << "start-shape was already loaded"; d->shape1->addDependee(this); if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { debugFlake << "connecting to start-shape"; d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); debugFlake << "start handle position =" << d->handles[StartHandle]; } } else { debugFlake << "start-shape not loaded yet, deferring connection"; context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First)); } } else { d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString()))); d->handles[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString()))); } if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) { d->connectionPointId2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt(); QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", ""); debugFlake << "references end-shape " << shapeId2 << "at glue-point" << d->connectionPointId2; d->shape2 = context.shapeById(shapeId2); if (d->shape2) { debugFlake << "end-shape was already loaded"; d->shape2->addDependee(this); if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { debugFlake << "connecting to end-shape"; d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); debugFlake << "end handle position =" << d->handles[EndHandle]; } } else { debugFlake << "end-shape not loaded yet, deferring connection"; context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second)); } } else { d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString()))); d->handles[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString()))); } QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString()); QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts); // TODO apply skew values once we support them // load the path data if there is any d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d"); if (d->hasCustomPath) { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); if (d->subpaths.size() > 0) { QRectF viewBox = loadOdfViewbox(element); if (viewBox.isEmpty()) { // there should be a viewBox to transform the path data // if there is none, use the bounding rectangle of the parsed path viewBox = outline().boundingRect(); } // convert path to viewbox coordinates to have a bounding rect of (0,0 1x1) // which can later be fitted back into the target rect once we have all // the required information QTransform viewMatrix; viewMatrix.scale(viewBox.width() ? static_cast(1.0) / viewBox.width() : 1.0, viewBox.height() ? static_cast(1.0) / viewBox.height() : 1.0); viewMatrix.translate(-viewBox.left(), -viewBox.top()); d->map(viewMatrix); // trigger finishing the connections in case we have all data // otherwise it gets called again once the shapes we are // connected to are loaded } else { d->hasCustomPath = false; } finishLoadingConnection(); } else { d->forceUpdate = true; updateConnections(); } loadText(element, context); return true; } void KoConnectionShape::finishLoadingConnection() { Q_D(KoConnectionShape); if (d->hasCustomPath) { const bool loadingFinished1 = d->connectionPointId1 >= 0 ? d->shape1 != 0 : true; const bool loadingFinished2 = d->connectionPointId2 >= 0 ? d->shape2 != 0 : true; if (loadingFinished1 && loadingFinished2) { QPointF p1, p2; if (d->handleConnected(StartHandle)) { if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { p1 = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); } } else { p1 = d->handles[StartHandle]; } if (d->handleConnected(EndHandle)) { if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); } } else { p2 = d->handles[EndHandle]; } QPointF relativeBegin = d->subpaths.first()->first()->point(); QPointF relativeEnd = d->subpaths.last()->last()->point(); QPointF diffRelative(relativeBegin - relativeEnd); QPointF diffAbsolute(p1 - p2); qreal factorX = diffRelative.x() ? diffAbsolute.x() / diffRelative.x(): 1.0; qreal factorY = diffRelative.y() ? diffAbsolute.y() / diffRelative.y(): 1.0; p1.setX(p1.x() - relativeBegin.x() * factorX); p1.setY(p1.y() - relativeBegin.y() * factorY); p2.setX(p2.x() + (1 - relativeEnd.x()) * factorX); p2.setY(p2.y() + (1 - relativeEnd.y()) * factorY); QRectF targetRect = QRectF(p1, p2).normalized(); // transform the normalized coordinates back to our target rectangle QTransform viewMatrix; viewMatrix.translate(targetRect.x(), targetRect.y()); viewMatrix.scale(targetRect.width(), targetRect.height()); d->map(viewMatrix); // pretend we are during a forced update, so normalize() // will not trigger an updateConnections() call d->forceUpdate = true; normalize(); d->forceUpdate = false; } } else { updateConnections(); } } void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); Q_D(KoConnectionShape); if (handleId >= d->handles.size()) return; d->handles[handleId] = point; } void KoConnectionShape::updatePath(const QSizeF &size) { Q_UNUSED(size); Q_D(KoConnectionShape); const qreal MinimumEscapeLength = (qreal)20.; clear(); switch (d->connectionType) { case Standard: { d->normalPath(MinimumEscapeLength); if (d->path.count() != 0){ moveTo(d->path[0]); for (int index = 1; index < d->path.count(); ++index) lineTo(d->path[index]); } break; } case Lines: { QPointF direction1 = d->escapeDirection(0); QPointF direction2 = d->escapeDirection(d->handles.count() - 1); moveTo(d->handles[StartHandle]); if (! direction1.isNull()) lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1); if (! direction2.isNull()) lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2); lineTo(d->handles[EndHandle]); break; } case Straight: moveTo(d->handles[StartHandle]); lineTo(d->handles[EndHandle]); break; case Curve: // TODO QPointF direction1 = d->escapeDirection(0); QPointF direction2 = d->escapeDirection(d->handles.count() - 1); moveTo(d->handles[StartHandle]); if (! direction1.isNull() && ! direction2.isNull()) { QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1; QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2; curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]); } else { lineTo(d->handles[EndHandle]); } break; } normalize(); } bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId) { Q_D(KoConnectionShape); // refuse to connect to a shape that depends on us (e.g. a artistic text shape) if (hasDependee(shape1)) return false; if (shape1) { // check if the connection point does exist if (!shape1->hasConnectionPoint(connectionPointId)) return false; // do not connect to the same connection point twice if (d->shape2 == shape1 && d->connectionPointId2 == connectionPointId) return false; } if (d->shape1) d->shape1->removeDependee(this); d->shape1 = shape1; if (d->shape1) d->shape1->addDependee(this); d->connectionPointId1 = connectionPointId; return true; } bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointId) { Q_D(KoConnectionShape); // refuse to connect to a shape that depends on us (e.g. a artistic text shape) if (hasDependee(shape2)) return false; if (shape2) { // check if the connection point does exist if (!shape2->hasConnectionPoint(connectionPointId)) return false; // do not connect to the same connection point twice if (d->shape1 == shape2 && d->connectionPointId1 == connectionPointId) return false; } if (d->shape2) d->shape2->removeDependee(this); d->shape2 = shape2; if (d->shape2) d->shape2->addDependee(this); d->connectionPointId2 = connectionPointId; return true; } KoShape *KoConnectionShape::firstShape() const { Q_D(const KoConnectionShape); return d->shape1; } int KoConnectionShape::firstConnectionId() const { Q_D(const KoConnectionShape); return d->connectionPointId1; } KoShape *KoConnectionShape::secondShape() const { Q_D(const KoConnectionShape); return d->shape2; } int KoConnectionShape::secondConnectionId() const { Q_D(const KoConnectionShape); return d->connectionPointId2; } KoConnectionShape::Type KoConnectionShape::type() const { Q_D(const KoConnectionShape); return d->connectionType; } void KoConnectionShape::setType(Type connectionType) { Q_D(KoConnectionShape); d->connectionType = connectionType; updatePath(size()); } void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape) { Q_D(KoConnectionShape); KoTosContainer::shapeChanged(type, shape); // check if we are during a forced update const bool updateIsActive = d->forceUpdate; switch (type) { case PositionChanged: case RotationChanged: case ShearChanged: case ScaleChanged: case GenericMatrixChange: case ParameterChanged: if (isParametricShape() && shape == 0) d->forceUpdate = true; break; case Deleted: if (shape != d->shape1 && shape != d->shape2) return; if (shape == d->shape1) connectFirst(0, -1); if (shape == d->shape2) connectSecond(0, -1); break; case ConnectionPointChanged: if (shape == d->shape1 && !shape->hasConnectionPoint(d->connectionPointId1)) { connectFirst(0, -1); } else if ( shape == d->shape2 && !shape->hasConnectionPoint(d->connectionPointId2)){ connectSecond(0, -1); } else { d->forceUpdate = true; } break; case BackgroundChanged: { // connection shape should not have a background QSharedPointer fill = background(); if (fill) { setBackground(QSharedPointer(0)); } return; } default: return; } // the connection was moved while it is connected to some other shapes const bool connectionChanged = !shape && (d->shape1 || d->shape2); // one of the connected shape has moved const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2); if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape()) updateConnections(); // reset the forced update flag d->forceUpdate = false; } QString KoConnectionShape::pathShapeId() const { return KOCONNECTIONSHAPEID; } diff --git a/libs/flake/KoGradientBackground.cpp b/libs/flake/KoGradientBackground.cpp index e8aa8f87d5..e6aeb1df62 100644 --- a/libs/flake/KoGradientBackground.cpp +++ b/libs/flake/KoGradientBackground.cpp @@ -1,194 +1,191 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoGradientBackground.h" #include "KoShapeBackground_p.h" #include "KoFlake.h" #include #include #include #include #include #include #include #include #include class KoGradientBackgroundPrivate : public KoShapeBackgroundPrivate { public: KoGradientBackgroundPrivate() : gradient(0) {} QGradient *gradient; QTransform matrix; }; KoGradientBackground::KoGradientBackground(QGradient * gradient, const QTransform &matrix) : KoShapeBackground(*(new KoGradientBackgroundPrivate())) { Q_D(KoGradientBackground); d->gradient = gradient; d->matrix = matrix; Q_ASSERT(d->gradient); - Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode); } KoGradientBackground::KoGradientBackground(const QGradient & gradient, const QTransform &matrix) : KoShapeBackground(*(new KoGradientBackgroundPrivate())) { Q_D(KoGradientBackground); d->gradient = KoFlake::cloneGradient(&gradient); d->matrix = matrix; Q_ASSERT(d->gradient); - Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode); } KoGradientBackground::~KoGradientBackground() { Q_D(KoGradientBackground); delete d->gradient; } bool KoGradientBackground::compareTo(const KoShapeBackground *other) const { Q_D(const KoGradientBackground); const KoGradientBackground *otherGradient = dynamic_cast(other); return otherGradient && d->matrix == otherGradient->d_func()->matrix && *d->gradient == *otherGradient->d_func()->gradient; } void KoGradientBackground::setTransform(const QTransform &matrix) { Q_D(KoGradientBackground); d->matrix = matrix; } QTransform KoGradientBackground::transform() const { Q_D(const KoGradientBackground); return d->matrix; } void KoGradientBackground::setGradient(const QGradient &gradient) { Q_D(KoGradientBackground); delete d->gradient; d->gradient = KoFlake::cloneGradient(&gradient); Q_ASSERT(d->gradient); - Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode); } const QGradient * KoGradientBackground::gradient() const { Q_D(const KoGradientBackground); return d->gradient; } void KoGradientBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { Q_D(const KoGradientBackground); if (!d->gradient) return; if (d->gradient->coordinateMode() == QGradient::ObjectBoundingMode) { /** * NOTE: important hack! * * Qt has different notation of QBrush::setTransform() in comparison * to what SVG defines. SVG defines gradientToUser matrix to be postmultiplied * by QBrush::transform(), but Qt does exectly reverse! * * That most probably has beed caused by the fact that Qt uses transposed * matrices and someone just mistyped the stuff long ago :( * * So here we basically emulate this feature by converting the gradient into * QGradient::LogicalMode and doing transformations manually. */ const QRectF boundingRect = fillPath.boundingRect(); QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // TODO: how about slicing the object? QGradient g = *d->gradient; g.setCoordinateMode(QGradient::LogicalMode); QBrush b(g); b.setTransform(d->matrix * gradientToUser); painter.setBrush(b); } else { QBrush b(*d->gradient); b.setTransform(d->matrix); painter.setBrush(b); } painter.drawPath(fillPath); } void KoGradientBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoGradientBackground); if (!d->gradient) return; QBrush brush(*d->gradient); brush.setTransform(d->matrix); KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), brush); } bool KoGradientBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { Q_D(KoGradientBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "gradient") { QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.stylesReader(), shapeSize); const QGradient * gradient = brush.gradient(); if (gradient) { d->gradient = KoFlake::cloneGradient(gradient); d->matrix = brush.transform(); //Gopalakrishna Bhat: If the brush has transparency then we ignore the draw:opacity property and use the brush transparency. // Brush will have transparency if the svg:linearGradient stop point has stop-opacity property otherwise it is opaque if (brush.isOpaque() && styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacityPercent = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacityPercent.isEmpty() && opacityPercent.right(1) == "%") { float opacity = qMin(opacityPercent.left(opacityPercent.length() - 1).toDouble(), 100.0) / 100; QGradientStops stops; Q_FOREACH (QGradientStop stop, d->gradient->stops()) { stop.second.setAlphaF(opacity); stops << stop; } d->gradient->setStops(stops); } } return true; } } return false; } diff --git a/libs/flake/KoHatchBackground.cpp b/libs/flake/KoHatchBackground.cpp index 9f08ed1c05..f556f127b5 100644 --- a/libs/flake/KoHatchBackground.cpp +++ b/libs/flake/KoHatchBackground.cpp @@ -1,235 +1,235 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoHatchBackground.h" #include "KoColorBackground_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KoHatchBackgroundPrivate : public KoColorBackgroundPrivate { public: KoHatchBackgroundPrivate() : angle(0.0) , distance(1.0) , style(KoHatchBackground::Single) {} QColor lineColor; int angle; qreal distance; KoHatchBackground::HatchStyle style; QString name; }; KoHatchBackground::KoHatchBackground() : KoColorBackground(*(new KoHatchBackgroundPrivate())) { } void KoHatchBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const { Q_D(const KoHatchBackground); if (d->color.isValid()) { // paint background color if set by using the color background KoColorBackground::paint(painter, converter, context, fillPath); } const QRectF targetRect = fillPath.boundingRect(); painter.save(); painter.setClipPath(fillPath); QPen pen(d->lineColor); // we set the pen width to 0.5 pt for the hatch. This is not defined in the spec. pen.setWidthF(0.5); painter.setPen(pen); QVector lines; // The different styles are handled by painting the lines multiple times with a different // angel offset as basically it just means we paint the lines also at a different angle. // This are the angle offsets we need to apply to the different lines of a style. // -90 is for single, 0 for the 2nd line in double and -45 for the 3th line in triple. const int angleOffset[] = {-90, 0, -45 }; // The number of loops is defined by the style. int loops = (d->style == Single) ? 1 : (d->style == Double) ? 2 : 3; for (int i = 0; i < loops; ++i) { int angle = d->angle - angleOffset[i]; qreal cosAngle = ::cos(angle/180.0*M_PI); // if cos is nearly 0 the lines are horizontal. Use a special case for that if (qAbs(cosAngle) > 0.00001) { qreal xDiff = tan(angle/180.0*M_PI) * targetRect.height(); // calculate the distance we need to increase x when creating the lines so that the // distance between the lines is also correct for rotated lines. qreal xOffset = qAbs(d->distance / cosAngle); // if the lines go to the right we need to start more to the left. Get the correct start. qreal xStart = 0; while (-xDiff < xStart) { xStart -= xOffset; } // if the lines go to the left we need to stop more at the right. Get the correct end offset qreal xEndOffset = 0; if (xDiff < 0) { while (xDiff < -xEndOffset) { xEndOffset += xOffset; } } // create line objects. lines.reserve(lines.size() + int((targetRect.width() + xEndOffset - xStart) / xOffset) + 1); for (qreal x = xStart; x < targetRect.width() + xEndOffset; x += xOffset) { lines.append(QLineF(x, 0, x + xDiff, targetRect.height())); } } else { // horizontal lines lines.reserve(lines.size() + int(targetRect.height()/d->distance) + 1); for (qreal y = 0; y < targetRect.height(); y += d->distance) { lines.append(QLineF(0, y, targetRect.width(), y)); } } } painter.drawLines(lines); painter.restore(); } void KoHatchBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoHatchBackground); KoGenStyle::Type type = style.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; style.addProperty("draw:fill", "hatch", propertyType); style.addProperty("draw:fill-hatch-name", saveHatchStyle(context), propertyType); bool fillHatchSolid = d->color.isValid(); style.addProperty("draw:fill-hatch-solid", fillHatchSolid, propertyType); if (fillHatchSolid) { style.addProperty("draw:fill-color", d->color.name(), propertyType); } } QString KoHatchBackground::saveHatchStyle(KoShapeSavingContext &context) const { Q_D(const KoHatchBackground); KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/); hatchStyle.addAttribute("draw:display-name", d->name); hatchStyle.addAttribute("draw:color", d->lineColor.name()); - hatchStyle.addAttributePt("draw:distance", d->distance); + hatchStyle.addAttribute("draw:distance", d->distance); hatchStyle.addAttribute("draw:rotation", QString("%1").arg(d->angle * 10)); switch (d->style) { case Single: hatchStyle.addAttribute("draw:style", "single"); break; case Double: hatchStyle.addAttribute("draw:style", "double"); break; case Triple: hatchStyle.addAttribute("draw:style", "triple"); break; } return context.mainStyles().insert(hatchStyle, "hatch"); } bool KoHatchBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { // Q_D(KoHatchBackground); Q_UNUSED(shapeSize); KoStyleStack &styleStack = context.styleStack(); QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "hatch") { QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name"); debugFlake << " hatch style is :" << style; KoXmlElement* draw = context.stylesReader().drawStyles("hatch")[style]; if (draw) { debugFlake << "Hatch style found for:" << style; QString angle = draw->attributeNS(KoXmlNS::draw, "rotation", QString("0")); if (angle.at(angle.size()-1).isLetter()) { d->angle = KoUnit::parseAngle(angle); } else { // OO saves the angle value without unit and multiplied by a factor of 10 d->angle = int(angle.toInt() / 10); } debugFlake << "angle :" << d->angle; d->name = draw->attributeNS(KoXmlNS::draw, "display-name"); // use 2mm as default, just in case it is not given in a document so we show something sensible. d->distance = KoUnit::parseValue(draw->attributeNS(KoXmlNS::draw, "distance", "2mm")); bool fillHatchSolid = styleStack.property(KoXmlNS::draw, "fill-hatch-solid") == QLatin1String("true"); if (fillHatchSolid) { QString fillColor = styleStack.property(KoXmlNS::draw, "fill-color"); if (!fillColor.isEmpty()) { d->color.setNamedColor(fillColor); } else { d->color =QColor(); } } else { d->color = QColor(); } d->lineColor.setNamedColor(draw->attributeNS(KoXmlNS::draw, "color", QString("#000000"))); QString style = draw->attributeNS(KoXmlNS::draw, "style", QString()); if (style == "double") { d->style = Double; } else if (style == "triple") { d->style = Triple; } else { d->style = Single; } } return true; } return false; } diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index 54880dfa55..dc10ed64ab 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1762 +1,1762 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" #include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoViewConverter.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include "KisQPainterStateSaver.h" #include #include #include "kis_global.h" #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } static const qreal DefaultMarkerWidth = 3.0; KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q) : KoTosContainerPrivate(q), fillRule(Qt::OddEvenFill), autoFillMarkers(false) { } KoPathShapePrivate::KoPathShapePrivate(const KoPathShapePrivate &rhs, KoPathShape *q) : KoTosContainerPrivate(rhs, q), fillRule(rhs.fillRule), markersNew(rhs.markersNew), autoFillMarkers(rhs.autoFillMarkers) { Q_FOREACH (KoSubpath *subPath, rhs.subpaths) { KoSubpath *clonedSubPath = new KoSubpath(); Q_FOREACH (KoPathPoint *point, *subPath) { *clonedSubPath << new KoPathPoint(*point, q); } subpaths << clonedSubPath; } } QRectF KoPathShapePrivate::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } void KoPathShapePrivate::applyViewboxTransformation(const KoXmlElement &element) { // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { // load the desired size QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // load the desired position QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); viewMatrix.translate(pos.x(), pos.y()); // transform the path data map(viewMatrix); } } KoPathShape::KoPathShape() :KoTosContainer(new KoPathShapePrivate(this)) { } KoPathShape::KoPathShape(KoPathShapePrivate *dd) : KoTosContainer(dd) { } KoPathShape::KoPathShape(const KoPathShape &rhs) : KoTosContainer(new KoPathShapePrivate(*rhs.d_func(), this)) { } KoPathShape::~KoPathShape() { clear(); } KoShape *KoPathShape::cloneShape() const { return new KoPathShape(*this); } void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const { Q_D(const KoPathShape); if (d->subpaths.length() <= 1) { QTransform matrix; matrix.scale(scaleFactor.width(), scaleFactor.height()); QString points; KoSubpath *subPath = d->subpaths.first(); KoSubpath::const_iterator pointIt(subPath->constBegin()); KoPathPoint *currPoint= 0; // iterate over all points for (; pointIt != subPath->constEnd(); ++pointIt) { currPoint = *pointIt; if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { break; } const QPointF p = matrix.map(currPoint->point()); points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); } if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { context.xmlWriter().startElement("draw:contour-polygon"); - context.xmlWriter().addAttributePt("svg:width", size().width()); - context.xmlWriter().addAttributePt("svg:height", size().height()); + context.xmlWriter().addAttribute("svg:width", size().width()); + context.xmlWriter().addAttribute("svg:height", size().height()); const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); context.xmlWriter().addAttribute("draw:points", points); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); return; } } // if we get here we couldn't save as polygon - let-s try contour-path context.xmlWriter().startElement("draw:contour-path"); saveOdfAttributes(context, OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); } void KoPathShape::saveOdf(KoShapeSavingContext & context) const { Q_D(const KoPathShape); context.xmlWriter().startElement("draw:path"); saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) { Q_D(KoPathShape); // first clear the path data from the default path clear(); if (element.localName() == "contour-polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } close(); } else if (element.localName() == "contour-path") { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); // transform the path data d->map(viewMatrix); } setTransformation(QTransform()); return true; } bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoPathShape); loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); // first clear the path data from the default path clear(); if (element.localName() == "line") { QPointF start; start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); QPointF end; end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); moveTo(start); lineTo(end); } else if (element.localName() == "polyline" || element.localName() == "polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } if (element.localName() == "polygon") close(); } else { // path loading KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } d->applyViewboxTransformation(element); QPointF pos = normalize(); setTransformation(QTransform()); if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); loadOdfAttributes(element, context, OdfTransformation); // now that the correct transformation is set up // apply that matrix to the path geometry so that // we don't transform the stroke d->map(transformation()); setTransformation(QTransform()); normalize(); loadText(element, context); return true; } QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoPathShape); style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth) return KoTosContainer::saveStyle(style, context); } void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoPathShape); KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; } else { d->fillRule = Qt::WindingFill; #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); #endif } QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth); } QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) { QRect viewbox; QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); if (! data.isEmpty()) { data.replace(QLatin1Char(','), QLatin1Char(' ')); const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); if (coordinates.count() == 4) { viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), coordinates.at(2).toInt(), coordinates.at(3).toInt()); } } return viewbox; } void KoPathShape::clear() { Q_D(KoPathShape); Q_FOREACH (KoSubpath *subpath, d->subpaths) { Q_FOREACH (KoPathPoint *point, *subpath) delete point; delete subpath; } d->subpaths.clear(); notifyPointsChanged(); } void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_D(KoPathShape); KisQPainterStateSaver saver(&painter); applyConversion(painter, converter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, converter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG void KoPathShapePrivate::paintDebug(QPainter &painter) { Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); int i = 0; QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } void KoPathShapePrivate::debugPath() const { Q_Q(const KoPathShape); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper) { Q_D(KoPathShape); KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(handlesHelper, KoPathPoint::Node); } } QRectF KoPathShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoPathShape::outline() const { Q_D(const KoPathShape); QPainterPath path; Q_FOREACH (KoSubpath * subpath, d->subpaths) { KoPathPoint * lastPoint = subpath->first(); bool activeCP = false; Q_FOREACH (KoPathPoint * currPoint, *subpath) { KoPathPoint::PointProperties currProperties = currPoint->properties(); if (currPoint == subpath->first()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( lastPoint->controlPoint2(), currPoint->controlPoint1(), currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( currPoint->controlPoint2(), firstPoint->controlPoint1(), firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { const QTransform transform = absoluteTransformation(0); /** * First we approximate the insets of the stroke by rendering a fat bezier curve * with width set to the maximum inset of miters and markers. The are swept by this * curve will be a good approximation of the real curve bounding rect. */ qreal outlineSweepWidth = 0; const QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); if (lineBorder) { outlineSweepWidth = lineBorder->lineWidth(); } if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom}); // insets extend outside the shape, but width extends both inside and outside, // so we should multiply insets by 2.0 outlineSweepWidth = std::max({outlineSweepWidth, 2.0 * maxInset, 2.0 * stroke()->strokeMaxMarkersInset(this)}); } QPen pen(Qt::black, outlineSweepWidth); // select round joins and caps to ensure it sweeps exactly // 'outlineSweepWidth' pixels in every possible pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); QRectF bb = transform.map(pathStroke(pen)).boundingRect(); if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite reccursion return outlineRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { Q_D(KoPathShape); QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { Q_D(KoPathShape); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); d->subpaths.push_back(path); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { Q_D(KoPathShape); if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = d->subpaths.last()->last(); d->updateLast(&lastPoint); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { Q_D(KoPathShape); if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); d->updateLast(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { Q_D(KoPathShape); if (d->subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = d->subpaths.last()->last(); d->updateLast(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { Q_D(KoPathShape); if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = d->subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0.0) return pointCnt; sweepAngle = qBound(-360.0, sweepAngle, 360.0); if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good approximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { Q_D(KoPathShape); if (d->subpaths.empty()) { return; } d->closeSubpath(d->subpaths.last()); } void KoPathShape::closeMerge() { Q_D(KoPathShape); if (d->subpaths.empty()) { return; } d->closeMergeSubpath(d->subpaths.last()); } QPointF KoPathShape::normalize() { Q_D(KoPathShape); QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); d->shapeChanged(ContentChanged); return tl; } void KoPathShapePrivate::map(const QTransform &matrix) { Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { (*it)->map(matrix); } } } void KoPathShapePrivate::updateLast(KoPathPoint **lastPoint) { Q_Q(KoPathShape); // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath KoPathPoint *subpathStart = subpaths.last()->first(); // clone the first point of the subpath... KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, q); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { Q_D(const KoPathShape); QList result; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { Q_D(const KoPathShape); QList segments; int subpathCount = d->subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = d->subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { Q_D(const KoPathShape); for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) { KoSubpath * subpath = d->subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { Q_D(const KoPathShape); KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { Q_D(const KoPathShape); int i = 0; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { Q_D(const KoPathShape); return d->subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); notifyPointsChanged(); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); point->setParent(0); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } notifyPointsChanged(); return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one d->subpaths.insert(pointIndex.first + 1, newSubpath); notifyPointsChanged(); return true; } bool KoPathShape::join(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first Q_FOREACH (KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path d->subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; notifyPointsChanged(); return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= d->subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; d->subpaths.removeAt(oldSubpathIndex); d->subpaths.insert(newSubpathIndex, subpath); notifyPointsChanged(); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); d->closeSubpath(subpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); notifyPointsChanged(); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) { Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.removeAt(subpathIndex); } notifyPointsChanged(); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { Q_D(KoPathShape); if (subpathIndex < 0 || subpathIndex > d->subpaths.size()) return false; Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.insert(subpathIndex, subpath); notifyPointsChanged(); return true; } int KoPathShape::combine(KoPathShape *path) { Q_D(KoPathShape); int insertSegmentPosition = -1; if (!path) return insertSegmentPosition; QTransform pathMatrix = path->absoluteTransformation(0); QTransform myMatrix = absoluteTransformation(0).inverted(); Q_FOREACH (KoSubpath* subpath, path->d_func()->subpaths) { KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, this); newPoint->map(pathMatrix); newPoint->map(myMatrix); newSubpath->append(newPoint); } d->subpaths.append(newSubpath); if (insertSegmentPosition < 0) { insertSegmentPosition = d->subpaths.size() - 1; } } normalize(); notifyPointsChanged(); return insertSegmentPosition; } bool KoPathShape::separate(QList & separatedPaths) { Q_D(KoPathShape); if (! d->subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(0); Q_FOREACH (KoSubpath* subpath, d->subpaths) { KoPathShape *shape = new KoPathShape(); shape->setStroke(stroke()); shape->setBackground(background()); shape->setShapeId(shapeId()); shape->setZIndex(zIndex()); KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, shape); newPoint->map(myMatrix); newSubpath->append(newPoint); } shape->d_func()->subpaths.append(newSubpath); shape->normalize(); // NOTE: shape cannot have any listeners yet, so no notification about // points modification is needed separatedPaths.append(shape); } return true; } void KoPathShapePrivate::closeSubpath(KoSubpath *subpath) { Q_Q(KoPathShape); if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); q->notifyPointsChanged(); } void KoPathShapePrivate::closeMergeSubpath(KoSubpath *subpath) { Q_Q(KoPathShape); if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); q->notifyPointsChanged(); } else { closeSubpath(subpath); } } KoSubpath *KoPathShapePrivate::subPath(int subpathIndex) const { Q_Q(const KoPathShape); if (subpathIndex < 0 || subpathIndex >= subpaths.size()) return 0; return subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { Q_D(const KoPathShape); QString pathString; // iterate over all subpaths KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); if (!currPoint) { qWarning() << "Found a zero point in the shape's path!"; continue; } // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); pathString += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); pathString += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } pathString += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return pathString; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } QString KoPathShapePrivate::nodeTypes() const { Q_Q(const KoPathShape); QString types; KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } void KoPathShapePrivate::loadNodeTypes(const KoXmlElement &element) { Q_Q(KoPathShape); if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { Q_D(const KoPathShape); return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { Q_D(KoPathShape); d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->setShapeId(KoPathShapeId); //shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! shadow()) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos) { Q_D(KoPathShape); if (!marker && d->markersNew.contains(pos)) { d->markersNew.remove(pos); } else { d->markersNew[pos] = marker; } } KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const { Q_D(const KoPathShape); return d->markersNew[pos].data(); } bool KoPathShape::hasMarkers() const { Q_D(const KoPathShape); return !d->markersNew.isEmpty(); } bool KoPathShape::autoFillMarkers() const { Q_D(const KoPathShape); return d->autoFillMarkers; } void KoPathShape::setAutoFillMarkers(bool value) { Q_D(KoPathShape); d->autoFillMarkers = value; } void KoPathShape::recommendPointSelectionChange(const QList &newSelection) { Q_D(KoShape); Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->recommendPointSelectionChange(this, newSelection); } } } void KoPathShape::notifyPointsChanged() { Q_D(KoShape); Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->notifyPathPointsChanged(this); } } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { Q_D(const KoPathShape); if (d->subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); QPair firstSegments; QPair lastSegments; // TODO: these variables are never(!) initialized! KoPathPoint *firstPoint = 0; KoPathPoint *lastPoint = 0; KoPathPoint *secondPoint = 0; KoPathPoint *preLastPoint = 0; KoSubpath *firstSubpath = d->subpaths.first(); stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); // shortent the path to make it look nice // replace the point temporarily in case there is an arrow // BE AWARE: this changes the content of the path so that outline give the correct values. if (firstPoint) { firstSubpath->first() = firstSegments.second.first(); if (secondPoint) { (*firstSubpath)[1] = firstSegments.second.second(); } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = lastSegments.first.first(); } firstSubpath->last() = lastSegments.first.second(); } QPainterPath path = stroker.createStroke(outline()); if (firstPoint) { firstSubpath->first() = firstPoint; if (secondPoint) { (*firstSubpath)[1] = secondPoint; } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = preLastPoint; } firstSubpath->last() = lastPoint; } pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index e8b49e8895..40f88b3304 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2546 +1,2546 @@ /* 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 #include "KoOdfGradientBackground.h" #include // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), shadow(0), border(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q) : q_ptr(q), size(rhs.size), shapeId(rhs.shapeId), name(rhs.name), localMatrix(rhs.localMatrix), connectors(rhs.connectors), parent(0), // to be initialized later shapeManagers(), // to be initialized later toolDelegates(), // FIXME: how to initialize them? userData(rhs.userData ? rhs.userData->clone() : 0), stroke(rhs.stroke), fill(rhs.fill), inheritBackground(rhs.inheritBackground), inheritStroke(rhs.inheritStroke), dependees(), // FIXME: how to initialize them? shadow(0), // WARNING: not implemented in Krita border(0), // WARNING: not implemented in Krita clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0), clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0), additionalAttributes(rhs.additionalAttributes), additionalStyleAttributes(rhs.additionalStyleAttributes), filterEffectStack(0), // WARNING: not implemented in Krita transparency(rhs.transparency), hyperLink(rhs.hyperLink), zIndex(rhs.zIndex), runThrough(rhs.runThrough), visible(rhs.visible), printable(rhs.visible), geometryProtected(rhs.geometryProtected), keepAspect(rhs.keepAspect), selectable(rhs.selectable), detectCollision(rhs.detectCollision), protectContent(rhs.protectContent), textRunAroundSide(rhs.textRunAroundSide), textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft), textRunAroundDistanceTop(rhs.textRunAroundDistanceTop), textRunAroundDistanceRight(rhs.textRunAroundDistanceRight), textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom), textRunAroundThreshold(rhs.textRunAroundThreshold), textRunAroundContour(rhs.textRunAroundContour) { } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); /** * The shape must have already been detached from all the parents and * shape managers. Otherwise we migh accidentally request some RTTI * information, which is not available anymore (we are in d-tor). * * TL;DR: fix the code that caused this destruction without unparenting * instead of trying to remove these assert! */ KIS_SAFE_ASSERT_RECOVER (!parent) { parent->removeShape(q); } KIS_SAFE_ASSERT_RECOVER (shapeManagers.isEmpty()) { Q_FOREACH (KoShapeManager *manager, shapeManagers) { manager->shapeInterface()->notifyShapeDestructed(q); } shapeManagers.clear(); } if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); Q_FOREACH (KoShape * shape, dependees) { shape->shapeChanged(type, q); } Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { listener->notifyShapeChangedImpl(type, q); } } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape const qint16 KoShape::maxZIndex = std::numeric_limits::max(); const qint16 KoShape::minZIndex = std::numeric_limits::min(); KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate *dd) : d_ptr(dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); d->listeners.clear(); delete d_ptr; } KoShape *KoShape::cloneShape() const { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!"); qWarning() << shapeId() << "cannot be cloned"; return 0; } void KoShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(paintcontext); if (stroke()) { stroke()->paint(this, painter, converter); } } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QRectF KoShape::boundingRect(const QList &shapes) { QRectF boundingRect; Q_FOREACH (KoShape *shape, shapes) { boundingRect |= shape->boundingRect(); } return boundingRect; } QRectF KoShape::absoluteOutlineRect(KoViewConverter *converter) const { return absoluteTransformation(converter).map(outline()).boundingRect(); } QRectF KoShape::absoluteOutlineRect(const QList &shapes, KoViewConverter *converter) { QRectF absoluteOutlineRect; Q_FOREACH (KoShape *shape, shapes) { absoluteOutlineRect |= shape->absoluteOutlineRect(converter); } return absoluteOutlineRect; } QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { /** * WARNING: Our definition of zIndex is not yet compatible with SVG2's * definition. In SVG stacking context of groups with the same * zIndex are **merged**, while in Krita the contents of groups * is never merged. One group will always below than the other. * Therefore, when zIndex of two groups inside the same parent * coinside, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ /** * The algorithm below doesn't correctly handle the case when the two pointers actually * point to the same shape. So just check it in advance to guarantee strict weak ordering * relation requirement */ if (s1 == s2) return false; // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } bool KoShape::inheritsTransformFromAny(const QList ancestorsInQuestion) const { bool result = false; KoShape *shape = const_cast(this); while (shape) { KoShapeContainer *parent = shape->parent(); if (parent && !parent->inheritsTransform(shape)) { break; } if (ancestorsInQuestion.contains(shape)) { result = true; break; } shape = parent; } return result; } bool KoShape::hasCommonParent(const KoShape *shape) const { const KoShape *thisShape = this; while (thisShape) { const KoShape *otherShape = shape; while (otherShape) { if (thisShape == otherShape) { return true; } otherShape = otherShape->parent(); } thisShape = thisShape->parent(); } return false; } qint16 KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::updateAbsolute(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { Q_D(const KoShape); if (background()) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(false); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; } void KoShape::notifyChanged() { Q_D(KoShape); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData.data(); } bool KoShape::hasTransparency() const { Q_D(const KoShape); QSharedPointer bg = background(); return !bg || bg->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); d->shapeChanged(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->inheritBackground = false; d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); QSharedPointer bg; if (!d->inheritBackground) { bg = d->fill; } else if (parent()) { bg = parent()->background(); } return bg; } void KoShape::setInheritBackground(bool value) { Q_D(KoShape); d->inheritBackground = value; if (d->inheritBackground) { d->fill.clear(); } } bool KoShape::inheritBackground() const { Q_D(const KoShape); return d->inheritBackground; } void KoShape::setZIndex(qint16 zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (!recursive) return d->visible; if (!d->visible) return false; KoShapeContainer * parentShape = parent(); if (parentShape) { return parentShape->isVisible(true); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; d->shapeChanged(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { Q_D(const KoShape); KoShapeStrokeModelSP stroke; if (!d->inheritStroke) { stroke = d->stroke; } else if (parent()) { stroke = parent()->stroke(); } return stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { Q_D(KoShape); d->inheritStroke = false; d->stroke = stroke; d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setInheritStroke(bool value) { Q_D(KoShape); d->inheritStroke = value; if (d->inheritStroke) { d->stroke.clear(); } } bool KoShape::inheritStroke() const { Q_D(const KoShape); return d->inheritStroke; } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath.reset(clipPath); d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { Q_D(KoShape); d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { Q_D(const KoShape); return d->clipMask.data(); } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isShapeEditable(bool recursive) const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (recursive && d->parent) { return d->parent->isShapeEditable(true); } return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModelSP sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); d->stroke.clear(); if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); QSharedPointer stroke(new KoShapeStroke()); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { QSharedPointer stroke(new KoShapeStroke()); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return KoShapeStrokeModelSP(); } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse); d->clipPath.reset(clipPath); } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } - context.xmlWriter().addAttributePt("svg:width", s.width()); - context.xmlWriter().addAttributePt("svg:height", s.height()); + 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().addAttributePt("svg:x", p.x()); - context.xmlWriter().addAttributePt("svg:y", p.y()); + 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().addAttributePt("svg:x", matrix.dx()); - context.xmlWriter().addAttributePt("svg:y", matrix.dy()); + context.xmlWriter().addAttribute("svg:x", matrix.dx()); + context.xmlWriter().addAttribute("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { - context.xmlWriter().addAttributePt("svg:x", cp.value().position.x()); - context.xmlWriter().addAttributePt("svg:y", cp.value().position.y()); + 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: // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour whereas // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform()); KoShape::applyConversion(*painter, converter); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::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/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp index 3eb0259a5e..437428b1ca 100644 --- a/libs/flake/KoShapeGroup.cpp +++ b/libs/flake/KoShapeGroup.cpp @@ -1,286 +1,286 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeGroup.h" #include "KoShapeContainerModel.h" #include "KoShapeContainer_p.h" #include "KoShapeLayer.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include "KoShapeRegistry.h" #include "KoShapeStrokeModel.h" #include "KoShapeShadow.h" #include "KoInsets.h" #include #include class ShapeGroupContainerModel : public SimpleShapeContainerModel { public: ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {} ~ShapeGroupContainerModel() override {} ShapeGroupContainerModel(const ShapeGroupContainerModel &rhs, KoShapeGroup *group) : SimpleShapeContainerModel(rhs), m_group(group) { } void add(KoShape *child) override { SimpleShapeContainerModel::add(child); m_group->invalidateSizeCache(); } void remove(KoShape *child) override { SimpleShapeContainerModel::remove(child); m_group->invalidateSizeCache(); } void childChanged(KoShape *shape, KoShape::ChangeType type) override { SimpleShapeContainerModel::childChanged(shape, type); //debugFlake << type; switch (type) { case KoShape::PositionChanged: case KoShape::RotationChanged: case KoShape::ScaleChanged: case KoShape::ShearChanged: case KoShape::SizeChanged: case KoShape::GenericMatrixChange: case KoShape::ParameterChanged: case KoShape::ClipPathChanged : m_group->invalidateSizeCache(); break; default: break; } } private: // members KoShapeGroup * m_group; }; class KoShapeGroupPrivate : public KoShapeContainerPrivate { public: KoShapeGroupPrivate(KoShapeGroup *q) : KoShapeContainerPrivate(q) { model = new ShapeGroupContainerModel(q); } KoShapeGroupPrivate(const KoShapeGroupPrivate &rhs, KoShapeGroup *q) : KoShapeContainerPrivate(rhs, q) { ShapeGroupContainerModel *otherModel = dynamic_cast(rhs.model); KIS_ASSERT_RECOVER_RETURN(otherModel); model = new ShapeGroupContainerModel(*otherModel, q); } ~KoShapeGroupPrivate() override { } mutable QRectF savedOutlineRect; mutable bool sizeCached = false; void tryUpdateCachedSize() const; Q_DECLARE_PUBLIC(KoShapeGroup) }; KoShapeGroup::KoShapeGroup() : KoShapeContainer(new KoShapeGroupPrivate(this)) { } KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs) : KoShapeContainer(new KoShapeGroupPrivate(*rhs.d_func(), this)) { } KoShapeGroup::~KoShapeGroup() { } KoShape *KoShapeGroup::cloneShape() const { return new KoShapeGroup(*this); } void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } bool KoShapeGroup::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } void KoShapeGroupPrivate::tryUpdateCachedSize() const { Q_Q(const KoShapeGroup); if (!sizeCached) { QRectF bound; Q_FOREACH (KoShape *shape, q->shapes()) { bound |= shape->transformation().mapRect(shape->outlineRect()); } savedOutlineRect = bound; size = bound.size(); sizeCached = true; } } QSizeF KoShapeGroup::size() const { Q_D(const KoShapeGroup); d->tryUpdateCachedSize(); return d->size; } void KoShapeGroup::setSize(const QSizeF &size) { QSizeF oldSize = this->size(); if (!shapeCount() || oldSize.isNull()) return; const QTransform scale = QTransform::fromScale(size.width() / oldSize.width(), size.height() / oldSize.height()); setTransformation(scale * transformation()); KoShapeContainer::setSize(size); } QRectF KoShapeGroup::outlineRect() const { Q_D(const KoShapeGroup); d->tryUpdateCachedSize(); return d->savedOutlineRect; } QRectF KoShapeGroup::boundingRect() const { QRectF groupBound = KoShape::boundingRect(shapes()); if (shadow()) { KoInsets insets; shadow()->insets(insets); groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } return groupBound; } void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:g"); saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes); - context.xmlWriter().addAttributePt("svg:y", position().y()); + context.xmlWriter().addAttribute("svg:y", position().y()); QList shapes = this->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape* shape, shapes) { shape->saveOdf(context); } saveOdfCommonChildElements(context); context.xmlWriter().endElement(); } bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoShapeGroup); loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes | OdfCommonChildElements); KoXmlElement child; QMap usedLayers; forEachElement(child, element) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { KoShapeLayer *layer = dynamic_cast(shape->parent()); if (layer) { usedLayers[layer]++; } addShape(shape); } } KoShapeLayer *parent = 0; int maxUseCount = 0; // find most used layer and use this as parent for the group for (QMap::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { if (it.value() > maxUseCount) { maxUseCount = it.value(); parent = it.key(); } } setParent(parent); QRectF bound; bool boundInitialized = false; Q_FOREACH (KoShape * shape, shapes()) { if (! boundInitialized) { bound = shape->boundingRect(); boundInitialized = true; } else bound = bound.united(shape->boundingRect()); } setSize(bound.size()); d->sizeCached = true; setPosition(bound.topLeft()); Q_FOREACH (KoShape * shape, shapes()) shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft()); return true; } void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); switch (type) { case KoShape::StrokeChanged: break; default: break; } invalidateSizeCache(); } void KoShapeGroup::invalidateSizeCache() { Q_D(KoShapeGroup); d->sizeCached = false; } diff --git a/libs/flake/KoToolFactoryBase.h b/libs/flake/KoToolFactoryBase.h index eb33381818..3673bad272 100644 --- a/libs/flake/KoToolFactoryBase.h +++ b/libs/flake/KoToolFactoryBase.h @@ -1,240 +1,240 @@ /* This file is part of the KDE project * Copyright (c) 2004 Boudewijn Rempt * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_TOOL_FACTORY_H #define KO_TOOL_FACTORY_H #include "kritaflake_export.h" #include class KoCanvasBase; class KoToolBase; class QKeySequence; /** * A factory for KoToolBase objects. * * The baseclass for all tool plugins. Each plugin that ships a KoToolBase should also * ship a factory. That factory will extend this class and set variable data like * a toolTip and icon in the constructor of that extending class. * * An example usage would be:
-class MyToolFactory : public KoToolFactoryBase {
-public:
-    MyToolFactory(const QStringList&)
-        : KoToolFactoryBase("MyTool") {
-        setToolTip(i18n("Create object"));
-        setToolType("dynamic");
-        setPriority(5);
-    }
-    ~MyToolFactory() {}
-    KoToolBase *createTool(KoCanvasBase *canvas);
-};
-K_PLUGIN_FACTORY_WITH_JSON((MyToolFactoryFactory, "mytool.json", registerPlugin();)
+ * class MyToolFactory : public KoToolFactoryBase {
+ * public:
+ *   MyToolFactory(const QStringList&)
+ *       : KoToolFactoryBase("MyTool") {
+ *       setToolTip(i18n("Create object"));
+ *       setToolType("dynamic");
+ *       setPriority(5);
+ *   }
+ *   ~MyToolFactory() {}
+ *   KoToolBase *createTool(KoCanvasBase *canvas);
+ * };
+ * K_PLUGIN_FACTORY_WITH_JSON((MyToolFactoryFactory, "mytool.json", registerPlugin();)
 
*/ class KRITAFLAKE_EXPORT KoToolFactoryBase { public: /** * Create the new factory * @param id a string that will be used internally for referencing the tool, for * example for use by the KoToolBase::activateTemporary. */ explicit KoToolFactoryBase(const QString &id); virtual ~KoToolFactoryBase(); /** * Instantiate a new tool * @param canvas the canvas that the new tool will work on. Should be passed * to the constructor of the tool. * @return a new KoToolBase instance, or zero if the tool doesn't want to show up. */ virtual KoToolBase *createTool(KoCanvasBase *canvas) = 0; /** * return the id for the tool this factory creates. * @return the id for the tool this factory creates. */ QString id() const; /** * Returns The priority of this tool in its section in the toolbox * @return The priority of this tool. */ int priority() const; /** * returns the type of tool, used to group tools in the toolbox * @return the type of tool */ QString section() const; /** * return a translated tooltip Text * @return a translated tooltip Text */ QString toolTip() const; /** * return the basename of the icon for this tool * @return the basename of the icon for this tool */ QString iconName() const; /** * Return the id of the shape we can process. * This is the shape Id the tool we create is associated with. So a TextTool for a TextShape. * In combination with the toolType the following situations can occur;
TypeshapeIdResult
'main' Foo Tool will always be visible, but only active when shape with shapeId 'Foo' is in the selection.
'main' '' Tool will always be visible, but only active when at least one shape is selected
'main' 'flake/always' Tool will always be visible and enabled.
'main' 'flake/edit' Tool will be visible no matter which shape is selected (if any), but only be enabled when the current layer is editable.
'dynamic' Foo Tool will only be visible when shape with shapeId 'Foo' is in the selection.
'dynamic' '' Tool will always be visible. We recommend you don't use this one.
"comma separated list of application names" see main type Similar to the 'main' item if the application name matches with the current application. Otherwise it's similar to 'dynamic', but segmented in its own section. If the list includes 'dynamic' it's even added to the dynamic section, when not matching the application name
'other' any similar to the 'dynamic' items, but segmented in its own section.
n/a /always An activation shape id ending with '/always' will make the tool always visible and enabled.
* @see KoShapeFactoryBase::shapeId() * @see setActivationShapeId() * @return the id of a shape, or an empty string for all shapes. */ QString activationShapeId() const; /** * Return the default keyboard shortcut for activation of this tool (if * the shape this tool belongs to is active). * * See KoToolManager for use. * * @return the shortcut */ QKeySequence shortcut() const; /** * Returns the main toolType * Each tool has a toolType which it uses to be grouped in the toolbox. * The predefined areas are main and dynamic. "main" tools are always * shown. * * @see toolType() * @see setToolType() */ static QString mainToolType() { return "main"; } /** * Returns the navigation toolType * Each tool has a toolType which it uses to be grouped in the toolbox. * The predefined areas are main and dynamic. "navigation" tools are always * shown and are for tools that change the settings of the canvas, zoom, pan... * * @see toolType() * @see setToolType() */ static QString navigationToolType() { return "navigation"; } /** * Returns the dynamic toolType * Each tool has a toolType which it uses to be grouped in the toolbox. * The predefined areas are main and dynamic. Dynamic tools are hidden * until the shape they belong to is activated. * * @see toolType() * @see setToolType() */ static QString dynamicToolType() { return "dynamic"; } /** * Set the default shortcut for activation of this tool. */ void setShortcut(const QKeySequence & shortcut); protected: /** * Set the tooltip to be used for this tool * @param tooltip the tooltip */ void setToolTip(const QString &tooltip); /** * Set the toolType. used to group tools in the toolbox * @param toolType the toolType */ void setSection(const QString §ion); /** * Set an icon to be used in the toolBox. * @param iconName the basename (without extension) of the icon */ void setIconName(const char *iconName); void setIconName(const QString &iconName); /** * Set the priority of this tool, as it is shown in the toolBox; lower number means * it will be show more to the front of the list. * @param newPriority the priority */ void setPriority(int newPriority); /** * Set the id of the shape we can process. * This is the Id, as passed to the constructor of a KoShapeFactoryBase, that the tool * we create is associated with. This means that if a KoTextShape is selected, then * all tools that have its id set here will be added to the dynamic part of the toolbox. * @param activationShapeId the Id of the shape * @see activationShapeId() */ void setActivationShapeId(const QString &activationShapeId); private: class Private; Private * const d; }; #endif diff --git a/libs/flake/svg/SvgStyleWriter.cpp b/libs/flake/svg/SvgStyleWriter.cpp index 0fd8c448fe..2eb0780d29 100644 --- a/libs/flake/svg/SvgStyleWriter.cpp +++ b/libs/flake/svg/SvgStyleWriter.cpp @@ -1,554 +1,552 @@ /* This file is part of the KDE project Copyright (C) 2002 Lars Siebold Copyright (C) 2002-2003,2005 Rob Buis Copyright (C) 2002,2005-2006 David Faure Copyright (C) 2002 Werner Trobin Copyright (C) 2002 Lennart Kudling Copyright (C) 2004 Nicolas Goutte Copyright (C) 2005 Boudewijn Rempt Copyright (C) 2005 Raphael Langerhorst Copyright (C) 2005 Thomas Zander Copyright (C) 2005,2007-2008 Jan Hambrecht Copyright (C) 2006 Inge Wallin Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Gábor Lehel Copyright (C) 2006 Laurent Montel Copyright (C) 2006 Christian Mueller Copyright (C) 2006 Ariya Hidayat 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 "SvgStyleWriter.h" #include "SvgSavingContext.h" #include "SvgUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "kis_algebra_2d.h" #include #include void SvgStyleWriter::saveSvgStyle(KoShape *shape, SvgSavingContext &context) { saveSvgBasicStyle(shape, context); saveSvgFill(shape, context); saveSvgStroke(shape, context); saveSvgEffects(shape, context); saveSvgClipping(shape, context); saveSvgMasking(shape, context); saveSvgMarkers(shape, context); } void SvgStyleWriter::saveSvgBasicStyle(KoShape *shape, SvgSavingContext &context) { if (!shape->isVisible(false)) { context.shapeWriter().addAttribute("display", "none"); } else if (shape->transparency() > 0.0) { context.shapeWriter().addAttribute("opacity", 1.0 - shape->transparency()); } } void SvgStyleWriter::saveSvgFill(KoShape *shape, SvgSavingContext &context) { if (! shape->background()) { context.shapeWriter().addAttribute("fill", "none"); } QBrush fill(Qt::NoBrush); QSharedPointer cbg = qSharedPointerDynamicCast(shape->background()); if (cbg) { context.shapeWriter().addAttribute("fill", cbg->color().name()); if (cbg->color().alphaF() < 1.0) context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF()); } QSharedPointer gbg = qSharedPointerDynamicCast(shape->background()); if (gbg) { QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context); context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")"); } QSharedPointer pbg = qSharedPointerDynamicCast(shape->background()); if (pbg) { const QString patternId = saveSvgPattern(pbg, shape, context); context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")"); } QSharedPointer vpbg = qSharedPointerDynamicCast(shape->background()); if (vpbg) { const QString patternId = saveSvgVectorPattern(vpbg, shape, context); context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")"); } KoPathShape * path = dynamic_cast(shape); if (path && shape->background()) { // non-zero is default, so only write fillrule if evenodd is set if (path->fillRule() == Qt::OddEvenFill) context.shapeWriter().addAttribute("fill-rule", "evenodd"); } } void SvgStyleWriter::saveSvgStroke(KoShape *shape, SvgSavingContext &context) { const QSharedPointer lineBorder = qSharedPointerDynamicCast(shape->stroke()); if (! lineBorder) return; QString strokeStr("none"); if (lineBorder->lineBrush().gradient()) { QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context); strokeStr = "url(#" + gradientId + ")"; } else { strokeStr = lineBorder->color().name(); } if (!strokeStr.isEmpty()) context.shapeWriter().addAttribute("stroke", strokeStr); if (lineBorder->color().alphaF() < 1.0) context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF()); context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth())); if (lineBorder->capStyle() == Qt::FlatCap) context.shapeWriter().addAttribute("stroke-linecap", "butt"); else if (lineBorder->capStyle() == Qt::RoundCap) context.shapeWriter().addAttribute("stroke-linecap", "round"); else if (lineBorder->capStyle() == Qt::SquareCap) context.shapeWriter().addAttribute("stroke-linecap", "square"); if (lineBorder->joinStyle() == Qt::MiterJoin) { context.shapeWriter().addAttribute("stroke-linejoin", "miter"); context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit()); } else if (lineBorder->joinStyle() == Qt::RoundJoin) context.shapeWriter().addAttribute("stroke-linejoin", "round"); else if (lineBorder->joinStyle() == Qt::BevelJoin) context.shapeWriter().addAttribute("stroke-linejoin", "bevel"); // dash if (lineBorder->lineStyle() > Qt::SolidLine) { qreal dashFactor = lineBorder->lineWidth(); if (lineBorder->dashOffset() != 0) context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset()); QString dashStr; const QVector dashes = lineBorder->lineDashes(); int dashCount = dashes.size(); for (int i = 0; i < dashCount; ++i) { if (i > 0) dashStr += ","; dashStr += QString("%1").arg(KisDomUtils::toString(dashes[i] * dashFactor)); } context.shapeWriter().addAttribute("stroke-dasharray", dashStr); } } void SvgStyleWriter::saveSvgEffects(KoShape *shape, SvgSavingContext &context) { KoFilterEffectStack * filterStack = shape->filterEffectStack(); if (!filterStack) return; QList filterEffects = filterStack->filterEffects(); if (!filterEffects.count()) return; const QString uid = context.createUID("filter"); filterStack->save(context.styleWriter(), uid); context.shapeWriter().addAttribute("filter", "url(#" + uid + ")"); } void embedShapes(const QList &shapes, KoXmlWriter &outWriter) { QBuffer buffer; buffer.open(QIODevice::WriteOnly); { SvgWriter shapesWriter(shapes); shapesWriter.saveDetached(buffer); } buffer.close(); outWriter.addCompleteElement(&buffer); } void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context) { KoClipPath *clipPath = shape->clipPath(); if (!clipPath) return; const QString uid = context.createUID("clippath"); context.styleWriter().startElement("clipPath"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates())); embedShapes(clipPath->clipShapes(), context.styleWriter()); context.styleWriter().endElement(); // clipPath context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")"); if (clipPath->clipRule() != Qt::WindingFill) context.shapeWriter().addAttribute("clip-rule", "evenodd"); } void SvgStyleWriter::saveSvgMasking(KoShape *shape, SvgSavingContext &context) { KoClipMask*clipMask = shape->clipMask(); if (!clipMask) return; const QString uid = context.createUID("clipmask"); context.styleWriter().startElement("mask"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("maskUnits", KoFlake::coordinateToString(clipMask->coordinates())); context.styleWriter().addAttribute("maskContentUnits", KoFlake::coordinateToString(clipMask->contentCoordinates())); const QRectF rect = clipMask->maskRect(); // think funny duplication? please note the 'pt' suffix! :) if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { context.styleWriter().addAttribute("x", rect.x()); context.styleWriter().addAttribute("y", rect.y()); context.styleWriter().addAttribute("width", rect.width()); context.styleWriter().addAttribute("height", rect.height()); } else { - context.styleWriter().addAttributePt("x", rect.x()); - context.styleWriter().addAttributePt("y", rect.y()); - context.styleWriter().addAttributePt("width", rect.width()); - context.styleWriter().addAttributePt("height", rect.height()); + context.styleWriter().addAttribute("x", rect.x()); + context.styleWriter().addAttribute("y", rect.y()); + context.styleWriter().addAttribute("width", rect.width()); + context.styleWriter().addAttribute("height", rect.height()); } embedShapes(clipMask->shapes(), context.styleWriter()); context.styleWriter().endElement(); // clipMask context.shapeWriter().addAttribute("mask", "url(#" + uid + ")"); } namespace { void writeMarkerStyle(KoXmlWriter &styleWriter, const KoMarker *marker, const QString &assignedId) { styleWriter.startElement("marker"); styleWriter.addAttribute("id", assignedId); styleWriter.addAttribute("markerUnits", KoMarker::coordinateSystemToString(marker->coordinateSystem())); const QPointF refPoint = marker->referencePoint(); styleWriter.addAttribute("refX", refPoint.x()); styleWriter.addAttribute("refY", refPoint.y()); const QSizeF refSize = marker->referenceSize(); styleWriter.addAttribute("markerWidth", refSize.width()); styleWriter.addAttribute("markerHeight", refSize.height()); if (marker->hasAutoOtientation()) { styleWriter.addAttribute("orient", "auto"); } else { // no suffix means 'degrees' styleWriter.addAttribute("orient", kisRadiansToDegrees(marker->explicitOrientation())); } embedShapes(marker->shapes(), styleWriter); styleWriter.endElement(); // marker } void tryEmbedMarker(const KoPathShape *pathShape, const QString &markerTag, KoFlake::MarkerPosition markerPosition, SvgSavingContext &context) { KoMarker *marker = pathShape->marker(markerPosition); if (marker) { const QString uid = context.createUID("lineMarker"); writeMarkerStyle(context.styleWriter(), marker, uid); context.shapeWriter().addAttribute(markerTag.toLatin1().data(), "url(#" + uid + ")"); } } } void SvgStyleWriter::saveSvgMarkers(KoShape *shape, SvgSavingContext &context) { KoPathShape *pathShape = dynamic_cast(shape); if (!pathShape || !pathShape->hasMarkers()) return; tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context); tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context); tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context); if (pathShape->autoFillMarkers()) { context.shapeWriter().addAttribute("krita:marker-fill-method", "auto"); } } void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context) { Q_FOREACH (const QGradientStop &stop, colorStops) { context.styleWriter().startElement("stop"); context.styleWriter().addAttribute("stop-color", stop.second.name()); context.styleWriter().addAttribute("offset", stop.first); context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF()); context.styleWriter().endElement(); } } inline QString convertGradientMode(QGradient::CoordinateMode mode) { KIS_ASSERT_RECOVER_NOOP(mode != QGradient::StretchToDeviceMode); return mode == QGradient::ObjectBoundingMode ? "objectBoundingBox" : "userSpaceOnUse"; } QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context) { if (! gradient) return QString(); - Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode); - const QString spreadMethod[3] = { QString("pad"), QString("reflect"), QString("repeat") }; const QString uid = context.createUID("gradient"); if (gradient->type() == QGradient::LinearGradient) { const QLinearGradient * g = static_cast(gradient); context.styleWriter().startElement("linearGradient"); context.styleWriter().addAttribute("id", uid); SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter()); context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode())); context.styleWriter().addAttribute("x1", g->start().x()); context.styleWriter().addAttribute("y1", g->start().y()); context.styleWriter().addAttribute("x2", g->finalStop().x()); context.styleWriter().addAttribute("y2", g->finalStop().y()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } else if (gradient->type() == QGradient::RadialGradient) { const QRadialGradient * g = static_cast(gradient); context.styleWriter().startElement("radialGradient"); context.styleWriter().addAttribute("id", uid); SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter()); context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode())); context.styleWriter().addAttribute("cx", g->center().x()); context.styleWriter().addAttribute("cy", g->center().y()); context.styleWriter().addAttribute("fx", g->focalPoint().x()); context.styleWriter().addAttribute("fy", g->focalPoint().y()); context.styleWriter().addAttribute("r", g->radius()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } else if (gradient->type() == QGradient::ConicalGradient) { //const QConicalGradient * g = static_cast( gradient ); // fake conical grad as radial. // fugly but better than data loss. /* printIndentation( m_defs, m_indent2 ); *m_defs << "center().x() << "\" "; *m_defs << "cy=\"" << g->center().y() << "\" "; *m_defs << "fx=\"" << grad.focalPoint().x() << "\" "; *m_defs << "fy=\"" << grad.focalPoint().y() << "\" "; double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) ); *m_defs << "r=\"" << QString().setNum( r ) << "\" "; *m_defs << spreadMethod[g->spread()]; *m_defs << ">" << endl; // color stops getColorStops( gradient->stops() ); printIndentation( m_defs, m_indent2 ); *m_defs << "" << endl; *m_body << "url(#" << uid << ")"; */ } return uid; } QString SvgStyleWriter::saveSvgPattern(QSharedPointer pattern, KoShape *shape, SvgSavingContext &context) { const QString uid = context.createUID("pattern"); const QSizeF shapeSize = shape->size(); const QSizeF patternSize = pattern->patternDisplaySize(); const QSize imageSize = pattern->pattern().size(); // calculate offset in point QPointF offset = pattern->referencePointOffset(); offset.rx() = 0.01 * offset.x() * patternSize.width(); offset.ry() = 0.01 * offset.y() * patternSize.height(); // now take the reference point into account switch (pattern->referencePoint()) { case KoPatternBackground::TopLeft: break; case KoPatternBackground::Top: offset += QPointF(0.5 * shapeSize.width(), 0.0); break; case KoPatternBackground::TopRight: offset += QPointF(shapeSize.width(), 0.0); break; case KoPatternBackground::Left: offset += QPointF(0.0, 0.5 * shapeSize.height()); break; case KoPatternBackground::Center: offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height()); break; case KoPatternBackground::Right: offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height()); break; case KoPatternBackground::BottomLeft: offset += QPointF(0.0, shapeSize.height()); break; case KoPatternBackground::Bottom: offset += QPointF(0.5 * shapeSize.width(), shapeSize.height()); break; case KoPatternBackground::BottomRight: offset += QPointF(shapeSize.width(), shapeSize.height()); break; } offset = shape->absoluteTransformation(0).map(offset); context.styleWriter().startElement("pattern"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x())); context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y())); if (pattern->repeat() == KoPatternBackground::Stretched) { context.styleWriter().addAttribute("width", "100%"); context.styleWriter().addAttribute("height", "100%"); context.styleWriter().addAttribute("patternUnits", "objectBoundingBox"); } else { context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width())); context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height())); context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse"); } context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(KisDomUtils::toString(imageSize.width())).arg(KisDomUtils::toString(imageSize.height()))); //*m_defs << " patternContentUnits=\"userSpaceOnUse\""; context.styleWriter().startElement("image"); context.styleWriter().addAttribute("x", "0"); context.styleWriter().addAttribute("y", "0"); context.styleWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(imageSize.width()))); context.styleWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(imageSize.height()))); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (pattern->pattern().save(&buffer, "PNG")) { const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png"); context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64()); } context.styleWriter().endElement(); // image context.styleWriter().endElement(); // pattern return uid; } QString SvgStyleWriter::saveSvgVectorPattern(QSharedPointer pattern, KoShape *parentShape, SvgSavingContext &context) { const QString uid = context.createUID("pattern"); context.styleWriter().startElement("pattern"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("patternUnits", KoFlake::coordinateToString(pattern->referenceCoordinates())); context.styleWriter().addAttribute("patternContentUnits", KoFlake::coordinateToString(pattern->contentCoordinates())); const QRectF rect = pattern->referenceRect(); if (pattern->referenceCoordinates() == KoFlake::ObjectBoundingBox) { context.styleWriter().addAttribute("x", rect.x()); context.styleWriter().addAttribute("y", rect.y()); context.styleWriter().addAttribute("width", rect.width()); context.styleWriter().addAttribute("height", rect.height()); } else { - context.styleWriter().addAttributePt("x", rect.x()); - context.styleWriter().addAttributePt("y", rect.y()); - context.styleWriter().addAttributePt("width", rect.width()); - context.styleWriter().addAttributePt("height", rect.height()); + context.styleWriter().addAttribute("x", rect.x()); + context.styleWriter().addAttribute("y", rect.y()); + context.styleWriter().addAttribute("width", rect.width()); + context.styleWriter().addAttribute("height", rect.height()); } SvgUtil::writeTransformAttributeLazy("patternTransform", pattern->patternTransform(), context.styleWriter()); if (pattern->contentCoordinates() == KoFlake::ObjectBoundingBox) { // TODO: move this normalization into the KoVectorPatternBackground itself QList shapes = pattern->shapes(); QList clonedShapes; const QRectF dstShapeBoundingRect = parentShape->outlineRect(); const QTransform relativeToShape = KisAlgebra2D::mapToRect(dstShapeBoundingRect); const QTransform shapeToRelative = relativeToShape.inverted(); Q_FOREACH (KoShape *shape, shapes) { KoShape *clone = shape->cloneShape(); clone->applyAbsoluteTransformation(shapeToRelative); clonedShapes.append(clone); } embedShapes(clonedShapes, context.styleWriter()); qDeleteAll(clonedShapes); } else { QList shapes = pattern->shapes(); embedShapes(shapes, context.styleWriter()); } context.styleWriter().endElement(); // pattern return uid; } diff --git a/libs/flake/svg/SvgWriter.cpp b/libs/flake/svg/SvgWriter.cpp index 8097e70406..9cdf82133e 100644 --- a/libs/flake/svg/SvgWriter.cpp +++ b/libs/flake/svg/SvgWriter.cpp @@ -1,302 +1,302 @@ /* This file is part of the KDE project Copyright (C) 2002 Lars Siebold Copyright (C) 2002-2003,2005 Rob Buis Copyright (C) 2002,2005-2006 David Faure Copyright (C) 2002 Werner Trobin Copyright (C) 2002 Lennart Kudling Copyright (C) 2004 Nicolas Goutte Copyright (C) 2005 Boudewijn Rempt Copyright (C) 2005 Raphael Langerhorst Copyright (C) 2005 Thomas Zander Copyright (C) 2005,2007-2008 Jan Hambrecht Copyright (C) 2006 Inge Wallin Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Gábor Lehel Copyright (C) 2006 Laurent Montel Copyright (C) 2006 Christian Mueller Copyright (C) 2006 Ariya Hidayat 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 "SvgWriter.h" #include "SvgUtil.h" #include "SvgSavingContext.h" #include "SvgShape.h" #include "SvgStyleWriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include SvgWriter::SvgWriter(const QList &layers) : m_writeInlineImages(true) { Q_FOREACH (KoShapeLayer *layer, layers) m_toplevelShapes.append(layer); } SvgWriter::SvgWriter(const QList &toplevelShapes) : m_toplevelShapes(toplevelShapes) , m_writeInlineImages(true) { } SvgWriter::~SvgWriter() { } bool SvgWriter::save(const QString &filename, const QSizeF &pageSize, bool writeInlineImages) { QFile fileOut(filename); if (!fileOut.open(QIODevice::WriteOnly)) return false; m_writeInlineImages = writeInlineImages; const bool success = save(fileOut, pageSize); m_writeInlineImages = true; fileOut.close(); return success; } bool SvgWriter::save(QIODevice &outputDevice, const QSizeF &pageSize) { if (m_toplevelShapes.isEmpty()) { return false; } QTextStream svgStream(&outputDevice); svgStream.setCodec("UTF-8"); // standard header: svgStream << "" << endl; svgStream << "" << endl; // add some PR. one line is more than enough. svgStream << "" << endl; svgStream << "" << endl; { SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); } // end tag: svgStream << endl << "" << endl; return true; } bool SvgWriter::saveDetached(QIODevice &outputDevice) { if (m_toplevelShapes.isEmpty()) return false; SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); return true; } bool SvgWriter::saveDetached(SvgSavingContext &savingContext) { if (m_toplevelShapes.isEmpty()) return false; saveShapes(m_toplevelShapes, savingContext); return true; } void SvgWriter::saveShapes(const QList shapes, SvgSavingContext &savingContext) { // top level shapes Q_FOREACH (KoShape *shape, shapes) { KoShapeLayer *layer = dynamic_cast(shape); if(layer) { saveLayer(layer, savingContext); } else { KoShapeGroup *group = dynamic_cast(shape); if (group) saveGroup(group, savingContext); else saveShape(shape, savingContext); } } } void SvgWriter::saveLayer(KoShapeLayer *layer, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(layer)); QList sortedShapes = layer->shapes(); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * group = dynamic_cast(shape); if (group) saveGroup(group, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveGroup(KoShapeGroup * group, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(group)); SvgUtil::writeTransformAttributeLazy("transform", group->transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(group, context); QList sortedShapes = group->shapes(); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * childGroup = dynamic_cast(shape); if (childGroup) saveGroup(childGroup, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveShape(KoShape *shape, SvgSavingContext &context) { SvgShape *svgShape = dynamic_cast(shape); if (svgShape && svgShape->saveSvg(context)) return; KoPathShape * path = dynamic_cast(shape); if (path) { savePath(path, context); } else { // generic saving of shape via a switch element saveGeneric(shape, context); } } void SvgWriter::savePath(KoPathShape *path, SvgSavingContext &context) { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(path)); SvgUtil::writeTransformAttributeLazy("transform", path->transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(path, context); context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform())); context.shapeWriter().endElement(); } void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); const QRectF bbox = shape->boundingRect(); // paint shape to the image KoShapePainter painter; painter.setShapes(QList()<< shape); // generate svg from shape QBuffer svgBuffer; QSvgGenerator svgGenerator; svgGenerator.setOutputDevice(&svgBuffer); /** * HACK ALERT: Qt (and Krita 3.x) has a weird bug, it assumes that all font sizes are * defined in 96 ppi resolution, even though your the resolution in QSvgGenerator * is manually set to 72 ppi. So here we do a tricky thing: we set a fake resolution * to (72 * 72 / 96) = 54 ppi, which guarantees that the text, when painted in 96 ppi, * will be actually painted in 72 ppi. * * BUG: 389802 */ if (shape->shapeId() == "TextShapeID") { svgGenerator.setResolution(54); } QPainter svgPainter; svgPainter.begin(&svgGenerator); painter.paint(svgPainter, SvgUtil::toUserSpace(bbox).toRect(), bbox); svgPainter.end(); // remove anything before the start of the svg element from the buffer int startOfContent = svgBuffer.buffer().indexOf("0) { svgBuffer.buffer().remove(0, startOfContent); } // check if painting to svg produced any output if (svgBuffer.buffer().isEmpty()) { // prepare a transparent image, make it twice as big as the original size QImage image(2*bbox.size().toSize(), QImage::Format_ARGB32); image.fill(0); painter.paint(image); context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", context.getID(shape)); - context.shapeWriter().addAttributePt("x", bbox.x()); - context.shapeWriter().addAttributePt("y", bbox.y()); - context.shapeWriter().addAttributePt("width", bbox.width()); - context.shapeWriter().addAttributePt("height", bbox.height()); + context.shapeWriter().addAttribute("x", bbox.x()); + context.shapeWriter().addAttribute("y", bbox.y()); + context.shapeWriter().addAttribute("width", bbox.width()); + context.shapeWriter().addAttribute("height", bbox.height()); context.shapeWriter().addAttribute("xlink:href", context.saveImage(image)); context.shapeWriter().endElement(); // image } else { context.shapeWriter().addCompleteElement(&svgBuffer); } // TODO: once we support saving single (flat) odf files // we can embed these here to have full support for generic shapes } diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt index 43eec69c6a..0d56391469 100644 --- a/libs/flake/tests/CMakeLists.txt +++ b/libs/flake/tests/CMakeLists.txt @@ -1,116 +1,116 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests( TestPosition.cpp TestSelection.cpp TestPathTool.cpp TestShapeAt.cpp TestShapePainting.cpp TestKoShapeFactory.cpp TestKoShapeRegistry.cpp TestShapeContainer.cpp TestShapeGroupCommand.cpp TestShapeReorderCommand.cpp TestImageCollection.cpp TestResourceManager.cpp TestShapeBackgroundCommand.cpp TestShapeStrokeCommand.cpp TestShapeShadowCommand.cpp TestInputDevice.cpp TestSnapStrategy.cpp NAME_PREFIX "libs-kritaflake-" LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestPathShape.cpp +ecm_add_test(TestPathShape.cpp TEST_NAME libs-kritaflake-TestPathShape LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestControlPointMoveCommand.cpp +ecm_add_test(TestControlPointMoveCommand.cpp TEST_NAME libs-kritaflake-TestControlPointMoveCommand LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestPointTypeCommand.cpp +ecm_add_test(TestPointTypeCommand.cpp TEST_NAME libs-kritaflake-TestPointTypeCommand LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestPointRemoveCommand.cpp +ecm_add_test(TestPointRemoveCommand.cpp TEST_NAME libs-kritaflake-TestPointRemoveCommand LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestRemoveSubpathCommand.cpp +ecm_add_test(TestRemoveSubpathCommand.cpp TEST_NAME libs-kritaflake-TestRemoveSubpathCommand LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestPathSegment.cpp +ecm_add_test(TestPathSegment.cpp TEST_NAME libs-kritaflake-TestPathSegment LINK_LIBRARIES kritaflake Qt5::Test) -krita_add_broken_unit_test(TestSegmentTypeCommand.cpp +ecm_add_test(TestSegmentTypeCommand.cpp TEST_NAME libs-kritaflake-TestSegmentTypeCommand LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestPointMergeCommand.cpp TEST_NAME libs-kritaflake-TestPointMergeCommand LINK_LIBRARIES kritaflake Qt5::Test) ecm_add_test( TestKoDrag.cpp TEST_NAME libs-kritaflake-TestKoDrag LINK_LIBRARIES kritaflake Qt5::Test ) ecm_add_test( TestKoMarkerCollection.cpp TEST_NAME libs-kritaflake-TestKoMarkerCollection LINK_LIBRARIES kritaflake Qt5::Test ) ecm_add_test( TestSvgParser.cpp TEST_NAME libs-kritaflake-TestSvgParser LINK_LIBRARIES kritaflake Qt5::Test ) ecm_add_test( TestSvgParser.cpp TEST_NAME libs-kritaflake-TestSvgParserCloned LINK_LIBRARIES kritaflake Qt5::Test ) set_property(TARGET libs-kritaflake-TestSvgParserCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) ecm_add_test( TestSvgParser.cpp TEST_NAME libs-kritaflake-TestSvgParserRoundTrip LINK_LIBRARIES kritaflake Qt5::Test ) set_property(TARGET libs-kritaflake-TestSvgParserRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) ecm_add_test( TestSvgText.cpp TEST_NAME libs-kritaflake-TestSvgText LINK_LIBRARIES kritaflake Qt5::Test ) ecm_add_test( TestSvgText.cpp TEST_NAME libs-kritaflake-TestSvgTextCloned LINK_LIBRARIES kritaflake Qt5::Test ) set_property(TARGET libs-kritaflake-TestSvgTextCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) ecm_add_test( TestSvgText.cpp TEST_NAME libs-kritaflake-TestSvgTextRoundTrip LINK_LIBRARIES kritaflake Qt5::Test ) set_property(TARGET libs-kritaflake-TestSvgTextRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) diff --git a/libs/flake/tests/TestKoMarkerCollection.cpp b/libs/flake/tests/TestKoMarkerCollection.cpp index fe9bd06f43..de05a13edc 100644 --- a/libs/flake/tests/TestKoMarkerCollection.cpp +++ b/libs/flake/tests/TestKoMarkerCollection.cpp @@ -1,110 +1,111 @@ /* * 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 "TestKoMarkerCollection.h" #include #include #include #include #include #include #include #include "kis_debug.h" #include "../../sdk/tests/qimage_test_util.h" +#include #include void initMarkerCollection(KoMarkerCollection *collection) { QCOMPARE(collection->markers().size(), 1); const QString fileName = TestUtil::fetchDataFileLazy("test_markers.svg"); QVERIFY(QFileInfo(fileName).exists()); collection->loadMarkersFromFile(fileName); QCOMPARE(collection->markers().size(), 10); } void TestKoMarkerCollection::testLoadMarkersFromFile() { KoMarkerCollection collection; initMarkerCollection(&collection); } void TestKoMarkerCollection::testDeduplication() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); KoPathShape *shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); KoMarker *marker(new KoMarker()); marker->setAutoOrientation(true); marker->setShapes({shape1}); KoMarkerCollection collection; QCOMPARE(collection.markers().size(), 1); KoMarker *clonedMarker = new KoMarker(*marker); collection.addMarker(marker); QCOMPARE(collection.markers().size(), 2); collection.addMarker(marker); QCOMPARE(collection.markers().size(), 2); collection.addMarker(clonedMarker); QCOMPARE(collection.markers().size(), 2); } void testOneMarkerPosition(KoMarker *marker, KoFlake::MarkerPosition position, const QString &testName) { QImage image(30,30, QImage::Format_ARGB32); image.fill(0); QPainter painter(&image); QPen pen(Qt::black, 2); marker->drawPreview(&painter, image.rect(), pen, position); QVERIFY(TestUtil::checkQImage(image, "marker_collection", "preview", testName)); } void TestKoMarkerCollection::testMarkerBounds() { KoMarkerCollection collection; initMarkerCollection(&collection); QList allMarkers = collection.markers(); KoMarker *marker = allMarkers[3]; QCOMPARE(marker->boundingRect(1, 0).toAlignedRect(), QRect(-7,-3,9,6)); QCOMPARE(marker->boundingRect(1, M_PI).toAlignedRect(), QRect(-2,-3,9,6)); QCOMPARE(marker->outline(1, 0).boundingRect().toAlignedRect(), QRect(-6,-2,7,4)); QCOMPARE(marker->outline(1, M_PI).boundingRect().toAlignedRect(), QRect(-1,-2,7,4)); testOneMarkerPosition(marker, KoFlake::StartMarker, "start_marker"); testOneMarkerPosition(marker, KoFlake::MidMarker, "mid_marker"); testOneMarkerPosition(marker, KoFlake::EndMarker, "end_marker"); } -QTEST_MAIN(TestKoMarkerCollection) +KISTEST_MAIN(TestKoMarkerCollection) diff --git a/libs/flake/tests/TestKoShapeRegistry.cpp b/libs/flake/tests/TestKoShapeRegistry.cpp index 8bd1f74005..9352fbe33f 100644 --- a/libs/flake/tests/TestKoShapeRegistry.cpp +++ b/libs/flake/tests/TestKoShapeRegistry.cpp @@ -1,213 +1,213 @@ /* This file is part of the KDE project * Copyright (c) 2007 Boudewijn Rempt (boud@valdyas.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestKoShapeRegistry.h" #include #include #include #include #include #include #include #include #include #include #include "KoShapeRegistry.h" #include "KoShape.h" #include "KoPathShape.h" #include "KoShapeLoadingContext.h" #include #include #include "kis_debug.h" - +#include void TestKoShapeRegistry::testGetKoShapeRegistryInstance() { KoShapeRegistry * registry = KoShapeRegistry::instance(); QVERIFY(registry != 0); } void TestKoShapeRegistry::testCreateShapes() { QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream.setCodec("UTF-8"); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); KoShapeRegistry * registry = KoShapeRegistry::instance(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; KoOdfLoadingContext odfContext(stylesReader, 0); KoShapeLoadingContext shapeContext(odfContext, 0); KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext); QVERIFY(shape == 0); KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement(); shape = registry->createShapeFromOdf(pathElement, shapeContext); QVERIFY(shape != 0); QVERIFY(shape->shapeId() == KoPathShapeId); } void TestKoShapeRegistry::testCreateFramedShapes() { QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream.setCodec("UTF-8"); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); KoShapeRegistry * registry = KoShapeRegistry::instance(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; KoOdfLoadingContext odfContext(stylesReader, 0); KoShapeLoadingContext shapeContext(odfContext, 0); KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext); QVERIFY(shape == 0); KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement(); shape = registry->createShapeFromOdf(pathElement, shapeContext); QVERIFY(shape != 0); QVERIFY(shape->shapeId() == KoPathShapeId); } #include #include #include #include #include "../../sdk/tests/qimage_test_util.h" void TestKoShapeRegistry::testFramedSvgShapes() { QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream.setCodec("UTF-8"); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << " "; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); KoShapeRegistry * registry = KoShapeRegistry::instance(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; const QString resourcesBlob = TestUtil::fetchDataFileLazy("odf_frame_resource_store.zip"); QScopedPointer store(KoStore::createStore(resourcesBlob, KoStore::Read, "krita", KoStore::Zip)); QScopedPointer resourceManager(new KoDocumentResourceManager()); QScopedPointer document(new MockShapeController()); QScopedPointer canvas(new MockCanvas(document.data())); QScopedPointer shapeController(new KoShapeController(canvas.data(), document.data())); resourceManager->setShapeController(shapeController.data()); KoOdfLoadingContext odfContext(stylesReader, store.data()); KoShapeLoadingContext shapeContext(odfContext, resourceManager.data()); KoXmlElement frameElement = bodyElement.firstChild().firstChild().toElement(); QCOMPARE(frameElement.tagName(), QString("frame")); KoShape *shape = registry->createShapeFromOdf(frameElement, shapeContext); QVERIFY(shape); QCOMPARE(shape->absoluteOutlineRect(0), QRectF(83, 41, 226,141)); } -QTEST_MAIN(TestKoShapeRegistry) +KISTEST_MAIN(TestKoShapeRegistry) diff --git a/libs/flake/tests/TestPointMergeCommand.cpp b/libs/flake/tests/TestPointMergeCommand.cpp index 85f6f0f7c8..cd73dffafa 100644 --- a/libs/flake/tests/TestPointMergeCommand.cpp +++ b/libs/flake/tests/TestPointMergeCommand.cpp @@ -1,564 +1,564 @@ /* This file is part of the KDE project * Copyright (C) 2009 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 "TestPointMergeCommand.h" #include "KoPathPointMergeCommand.h" #include "KoPathShape.h" #include "KoPathPoint.h" #include "KoPathPointData.h" - +#include #include #include void TestPointMergeCommand::closeSingleLinePath() { KoPathShape path1; path1.moveTo(QPointF(40, 0)); path1.lineTo(QPointF(60, 0)); path1.lineTo(QPointF(60, 30)); path1.lineTo(QPointF(0, 30)); path1.lineTo(QPointF(0, 0)); path1.lineTo(QPointF(20, 0)); KoPathPointIndex index1(0,0); KoPathPointIndex index2(0,5); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); KoPathPoint * p1 = path1.pointByIndex(index1); KoPathPoint * p2 = path1.pointByIndex(index2); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); KoPathPointMergeCommand cmd1(pd1,pd2); cmd1.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 5); - QCOMPARE(p2->point(), QPointF(30,0)); + QCOMPARE(p2->point(), QPointF(20,0)); cmd1.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); KoPathPointMergeCommand cmd2(pd2,pd1); cmd2.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 5); - QCOMPARE(p2->point(), QPointF(30,0)); + QCOMPARE(p2->point(), QPointF(20,0)); cmd2.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); } void TestPointMergeCommand::closeSingleCurvePath() { KoPathShape path1; path1.moveTo(QPointF(40, 0)); path1.curveTo(QPointF(60, 0), QPointF(60,0), QPointF(60,60)); path1.lineTo(QPointF(0, 60)); path1.curveTo(QPointF(0, 0), QPointF(0,0), QPointF(20,0)); KoPathPointIndex index1(0,0); KoPathPointIndex index2(0,3); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); KoPathPoint * p1 = path1.pointByIndex(index1); KoPathPoint * p2 = path1.pointByIndex(index2); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); KoPathPointMergeCommand cmd1(pd1,pd2); cmd1.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 3); - QCOMPARE(p2->point(), QPointF(30,0)); + QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(p2->activeControlPoint1()); - QVERIFY(p2->activeControlPoint2()); + QVERIFY(!p2->activeControlPoint2()); cmd1.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); KoPathPointMergeCommand cmd2(pd2,pd1); cmd2.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 3); - QCOMPARE(p2->point(), QPointF(30,0)); + QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(p2->activeControlPoint1()); - QVERIFY(p2->activeControlPoint2()); + QVERIFY(!p2->activeControlPoint2()); cmd2.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); } void TestPointMergeCommand::connectLineSubpaths() { KoPathShape path1; path1.moveTo(QPointF(0,0)); path1.lineTo(QPointF(10,0)); path1.moveTo(QPointF(20,0)); path1.lineTo(QPointF(30,0)); KoPathPointIndex index1(0,1); KoPathPointIndex index2(1,0); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0)); cmd1.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); KoPathPointMergeCommand cmd2(pd2, pd1); cmd2.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0)); cmd2.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); } void TestPointMergeCommand::connectCurveSubpaths() { KoPathShape path1; path1.moveTo(QPointF(0,0)); path1.curveTo(QPointF(20,0),QPointF(0,20),QPointF(20,20)); path1.moveTo(QPointF(50,0)); path1.curveTo(QPointF(30,0), QPointF(50,20), QPointF(30,20)); KoPathPointIndex index1(0,1); KoPathPointIndex index2(1,1); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(path1.pointByIndex(index1)->activeControlPoint2()); cmd1.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); KoPathPointMergeCommand cmd2(pd2, pd1); cmd2.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(path1.pointByIndex(index1)->activeControlPoint2()); cmd2.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); } #include #include #include "kis_debug.h" #include void TestPointMergeCommand::testCombineShapes() { MockShapeController mockController; MockCanvas canvas(&mockController); QList shapes; for (int i = 0; i < 3; i++) { const QPointF step(15,15); const QRectF rect = QRectF(5,5,10,10).translated(step * i); QPainterPath p; p.addRect(rect); KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p); QCOMPARE(shape->absoluteOutlineRect(), rect); shapes << shape; mockController.addShape(shape); } KoPathCombineCommand cmd(&mockController, shapes); cmd.redo(); QCOMPARE(canvas.shapeManager()->shapes().size(), 1); KoPathShape *combinedShape = dynamic_cast(canvas.shapeManager()->shapes().first()); QCOMPARE(combinedShape, cmd.combinedPath()); QCOMPARE(combinedShape->subpathCount(), 3); QCOMPARE(combinedShape->absoluteOutlineRect(), QRectF(5,5,40,40)); QList tstPoints; QList expPoints; tstPoints << KoPathPointData(shapes[0], KoPathPointIndex(0,1)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(0,1)); tstPoints << KoPathPointData(shapes[1], KoPathPointIndex(0,2)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(1,2)); tstPoints << KoPathPointData(shapes[2], KoPathPointIndex(0,3)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(2,3)); for (int i = 0; i < tstPoints.size(); i++) { KoPathPointData convertedPoint = cmd.originalToCombined(tstPoints[i]); QCOMPARE(convertedPoint, expPoints[i]); } qDeleteAll(canvas.shapeManager()->shapes()); } #include #include #include #include "kis_algebra_2d.h" inline QPointF fetchPoint(KoPathShape *shape, int subpath, int pointIndex) { return shape->absoluteTransformation(0).map( shape->pointByIndex(KoPathPointIndex(subpath, pointIndex))->point()); } void dumpShape(KoPathShape *shape, const QString &fileName) { QImage tmp(50,50, QImage::Format_ARGB32); tmp.fill(0); QPainter p(&tmp); p.drawPath(shape->absoluteTransformation(0).map(shape->outline())); tmp.save(fileName); } template void testMultipathMergeShapesImpl(const int srcPointIndex1, const int srcPointIndex2, const QList &expectedResultPoints, bool singleShape = false) { MockShapeController mockController; MockCanvas canvas(&mockController); QList shapes; for (int i = 0; i < 3; i++) { const QPointF step(15,15); const QRectF rect = QRectF(5,5,10,10).translated(step * i); QPainterPath p; p.moveTo(rect.topLeft()); p.lineTo(rect.bottomRight()); p.lineTo(rect.topRight()); KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p); QCOMPARE(shape->absoluteOutlineRect(), rect); shapes << shape; mockController.addShape(shape); } { KoPathPointData pd1(shapes[0], KoPathPointIndex(0,srcPointIndex1)); KoPathPointData pd2(shapes[singleShape ? 0 : 1], KoPathPointIndex(0,srcPointIndex2)); MergeCommand cmd(pd1, pd2, &mockController, canvas.shapeManager()->selection()); cmd.redo(); const int expectedShapesCount = singleShape ? 3 : 2; QCOMPARE(canvas.shapeManager()->shapes().size(), expectedShapesCount); KoPathShape *combinedShape = 0; if (!singleShape) { combinedShape = dynamic_cast(canvas.shapeManager()->shapes()[1]); QCOMPARE(combinedShape, cmd.testingCombinedPath()); } else { combinedShape = dynamic_cast(canvas.shapeManager()->shapes()[0]); QCOMPARE(combinedShape, shapes[0]); } QCOMPARE(combinedShape->subpathCount(), 1); QRectF expectedOutlineRect; KisAlgebra2D::accumulateBounds(expectedResultPoints, &expectedOutlineRect); QVERIFY(KisAlgebra2D::fuzzyCompareRects(combinedShape->absoluteOutlineRect(), expectedOutlineRect, 0.01)); if (singleShape) { QCOMPARE(combinedShape->isClosedSubpath(0), true); } QCOMPARE(combinedShape->subpathPointCount(0), expectedResultPoints.size()); for (int i = 0; i < expectedResultPoints.size(); i++) { if (fetchPoint(combinedShape, 0, i) != expectedResultPoints[i]) { qDebug() << ppVar(i); qDebug() << ppVar(fetchPoint(combinedShape, 0, i)); qDebug() << ppVar(expectedResultPoints[i]); QFAIL("Resulting shape points are different!"); } } QList shapes = canvas.shapeManager()->selection()->selectedEditableShapes(); QCOMPARE(shapes.size(), 1); QCOMPARE(shapes.first(), combinedShape); //dumpShape(combinedShape, "tmp_0_seq.png"); cmd.undo(); QCOMPARE(canvas.shapeManager()->shapes().size(), 3); } } void TestPointMergeCommand::testMultipathMergeShapesBothSequential() { // both sequential testMultipathMergeShapesImpl(2, 0, { QPointF(5,5), QPointF(15,15), QPointF(17.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathMergeShapesFirstReversed() { // first reversed testMultipathMergeShapesImpl(0, 0, { QPointF(15,5), QPointF(15,15), QPointF(12.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathMergeShapesSecondReversed() { // second reversed testMultipathMergeShapesImpl(2, 2, { QPointF(5,5), QPointF(15,15), QPointF(22.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathMergeShapesBothReversed() { // both reversed testMultipathMergeShapesImpl(0, 2, { QPointF(15,5), QPointF(15,15), QPointF(17.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathMergeShapesSingleShapeEndToStart() { // close end->start testMultipathMergeShapesImpl(2, 0, { QPointF(15,15), QPointF(10,5) }, true); } void TestPointMergeCommand::testMultipathMergeShapesSingleShapeStartToEnd() { // close start->end testMultipathMergeShapesImpl(0, 2, { QPointF(15,15), QPointF(10,5) }, true); } void TestPointMergeCommand::testMultipathJoinShapesBothSequential() { // both sequential testMultipathMergeShapesImpl (2, 0, { QPointF(5,5), QPointF(15,15), QPointF(15,5), QPointF(20,20), QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathJoinShapesFirstReversed() { // first reversed testMultipathMergeShapesImpl (0, 0, { QPointF(15,5), QPointF(15,15), QPointF(5,5), QPointF(20,20), QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathJoinShapesSecondReversed() { // second reversed testMultipathMergeShapesImpl (2, 2, { QPointF(5,5), QPointF(15,15), QPointF(15,5), QPointF(30,20), QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathJoinShapesBothReversed() { // both reversed testMultipathMergeShapesImpl (0, 2, { QPointF(15,5), QPointF(15,15), QPointF(5,5), QPointF(30,20), QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathJoinShapesSingleShapeEndToStart() { // close end->start testMultipathMergeShapesImpl (2, 0, { QPointF(5,5), QPointF(15,15), QPointF(15,5) }, true); } void TestPointMergeCommand::testMultipathJoinShapesSingleShapeStartToEnd() { // close start->end testMultipathMergeShapesImpl (0, 2, { QPointF(5,5), QPointF(15,15), QPointF(15,5) }, true); } -QTEST_MAIN(TestPointMergeCommand) +KISTEST_MAIN(TestPointMergeCommand) diff --git a/libs/flake/tests/TestPointRemoveCommand.cpp b/libs/flake/tests/TestPointRemoveCommand.cpp index 0158d82849..0c3adcf0cf 100644 --- a/libs/flake/tests/TestPointRemoveCommand.cpp +++ b/libs/flake/tests/TestPointRemoveCommand.cpp @@ -1,303 +1,303 @@ /* This file is part of the KDE project Copyright (C) 2007 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 "TestPointRemoveCommand.h" #include #include "KoPathShape.h" #include "KoPathPointRemoveCommand.h" #include "KoShapeController.h" #include - +#include #include void TestPointRemoveCommand::redoUndoPointRemove() { KoPathShape path1; path1.moveTo(QPointF(0, 0)); path1.lineTo(QPointF(0, 100)); KoPathPoint *point1 = path1.curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point2 = path1.lineTo(QPointF(200, 100)); path1.curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); QPainterPath orig1(QPointF(0, 0)); orig1.lineTo(0, 100); orig1.cubicTo(0, 50, 100, 50, 100, 100); orig1.lineTo(200, 100); orig1.cubicTo(200, 50, 300, 50, 300, 100); QVERIFY(orig1 == path1.outline()); KoPathShape path2; path2.moveTo(QPointF(0, 0)); KoPathPoint *point3 = path2.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2.closeMerge(); QList pd; pd.append(KoPathPointData(&path1, path1.pathPointIndex(point1))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point2))); pd.append(KoPathPointData(&path2, path2.pathPointIndex(point3))); QPainterPath ppath1Org = path1.outline(); QPainterPath ppath2Org = path2.outline(); MockShapeController mockController; KoShapeController shapeController(0, &mockController); KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd->redo(); QPainterPath ppath1(QPointF(0, 0)); ppath1.lineTo(0, 100); ppath1.cubicTo(0, 50, 300, 50, 300, 100); QPainterPath ppath2(QPointF(0, 0)); ppath2.cubicTo(50, 0, 0, 50, 0, 0); ppath2.closeSubpath(); QVERIFY(ppath1 == path1.outline()); QVERIFY(ppath2 == path2.outline()); cmd->undo(); QVERIFY(ppath1Org == path1.outline()); QVERIFY(ppath2Org == path2.outline()); delete cmd; } void TestPointRemoveCommand::redoUndoSubpathRemove() { KoPathShape path1; KoPathPoint *point11 = path1.moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1.lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1.curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1.lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1.curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathPoint *point21 = path1.moveTo(QPointF(0, 0)); KoPathPoint *point22 = path1.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path1.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path1.closeMerge(); path1.moveTo(QPointF(100, 0)); path1.lineTo(QPointF(100, 100)); QList pd; pd.append(KoPathPointData(&path1, path1.pathPointIndex(point11))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point12))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point13))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point14))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point15))); QPainterPath ppath1Org = path1.outline(); MockShapeController mockController; KoShapeController shapeController(0, &mockController); KUndo2Command *cmd1 = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd1->redo(); QPainterPath ppath1(QPointF(0, 0)); ppath1.cubicTo(50, 0, 100, 50, 100, 100); ppath1.cubicTo(50, 100, 0, 50, 0, 0); ppath1.closeSubpath(); ppath1.moveTo(100, 0); ppath1.lineTo(100, 100); QPainterPath ppath1mod = path1.outline(); QVERIFY(ppath1 == ppath1mod); QList pd2; pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point21))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point22))); KUndo2Command *cmd2 = KoPathPointRemoveCommand::createCommand(pd2, &shapeController); cmd2->redo(); QPainterPath ppath2(QPointF(0, 0)); ppath2.lineTo(0, 100); QVERIFY(ppath2 == path1.outline()); cmd2->undo(); QVERIFY(ppath1mod == path1.outline()); cmd1->undo(); QVERIFY(ppath1Org == path1.outline()); delete cmd2; delete cmd1; } void TestPointRemoveCommand::redoUndoShapeRemove() { KoPathShape *path1 = new KoPathShape(); KoPathPoint *point11 = path1->moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1->lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1->curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1->lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1->curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathShape *path2 = new KoPathShape(); KoPathPoint *point21 = path2->moveTo(QPointF(0, 0)); KoPathPoint *point22 = path2->curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2->curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2->closeMerge(); QList pd; pd.append(KoPathPointData(path1, path1->pathPointIndex(point12))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point11))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point13))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point15))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point14))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point22))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point21))); QPainterPath ppath1Org = path1->outline(); QPainterPath ppath2Org = path2->outline(); MockShapeController mockController; mockController.addShape(path1); mockController.addShape(path2); KoShapeController shapeController(0, &mockController); KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd->redo(); QVERIFY(!mockController.contains(path1)); QVERIFY(!mockController.contains(path2)); cmd->undo(); QVERIFY(mockController.contains(path1)); QVERIFY(mockController.contains(path2)); QVERIFY(ppath1Org == path1->outline()); QVERIFY(ppath2Org == path2->outline()); delete cmd; delete path1; delete path2; } void TestPointRemoveCommand::redoUndo() { KoPathShape *path1 = new KoPathShape(); KoPathPoint *point11 = path1->moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1->lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1->curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1->lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1->curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathPoint *point16 = path1->moveTo(QPointF(100, 0)); KoPathPoint *point17 = path1->curveTo(QPointF(150, 0), QPointF(200, 50), QPointF(200, 100)); path1->curveTo(QPointF(150, 100), QPointF(100, 50), QPointF(100, 0)); path1->closeMerge(); KoPathPoint *point18 = path1->moveTo(QPointF(200, 0)); KoPathPoint *point19 = path1->lineTo(QPointF(200, 100)); KoPathShape *path2 = new KoPathShape(); KoPathPoint *point21 = path2->moveTo(QPointF(0, 0)); KoPathPoint *point22 = path2->curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2->curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2->closeMerge(); KoPathShape *path3 = new KoPathShape(); KoPathPoint *point31 = path3->moveTo(QPointF(0, 0)); KoPathPoint *point32 = path3->lineTo(QPointF(100, 100)); KoPathPoint *point33 = path3->lineTo(QPointF(200, 150)); MockShapeController mockController; mockController.addShape(path1); mockController.addShape(path2); mockController.addShape(path3); QList pd; pd.append(KoPathPointData(path2, path2->pathPointIndex(point21))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point13))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point11))); pd.append(KoPathPointData(path3, path3->pathPointIndex(point31))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point12))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point15))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point22))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point14))); KoShapeController shapeController(0, &mockController); QPainterPath ppath1Org = path1->outline(); QPainterPath ppath2Org = path2->outline(); QPainterPath ppath3Org = path3->outline(); KUndo2Command *cmd1 = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd1->redo(); QVERIFY(mockController.contains(path1)); QVERIFY(!mockController.contains(path2)); QVERIFY(mockController.contains(path3)); QPainterPath ppath1(QPointF(0, 0)); ppath1.cubicTo(50, 0, 100, 50, 100, 100); ppath1.cubicTo(50, 100, 0, 50, 0, 0); ppath1.closeSubpath(); ppath1.moveTo(100, 0); ppath1.lineTo(100, 100); QPainterPath ppath1mod = path1->outline(); QVERIFY(ppath1 == ppath1mod); QPainterPath ppath3(QPointF(0, 0)); ppath3.lineTo(100, 50); QPainterPath ppath3mod = path3->outline(); QVERIFY(ppath3 == ppath3mod); QList pd2; pd2.append(KoPathPointData(path1, path1->pathPointIndex(point16))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point17))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point18))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point19))); pd2.append(KoPathPointData(path3, path3->pathPointIndex(point32))); pd2.append(KoPathPointData(path3, path3->pathPointIndex(point33))); KUndo2Command *cmd2 = KoPathPointRemoveCommand::createCommand(pd2, &shapeController); cmd2->redo(); QVERIFY(!mockController.contains(path1)); QVERIFY(!mockController.contains(path2)); QVERIFY(!mockController.contains(path3)); cmd2->undo(); QVERIFY(mockController.contains(path1)); QVERIFY(!mockController.contains(path2)); QVERIFY(mockController.contains(path3)); QVERIFY(ppath1 == ppath1mod); QVERIFY(ppath3 == ppath3mod); cmd1->undo(); QVERIFY(ppath1Org == path1->outline()); QVERIFY(ppath2Org == path2->outline()); QVERIFY(ppath3Org == path3->outline()); cmd1->redo(); cmd2->redo(); delete cmd2; delete cmd1; } -QTEST_MAIN(TestPointRemoveCommand) +KISTEST_MAIN(TestPointRemoveCommand) diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index 8166f8d7f3..4dc6a8cdd6 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,324 +1,323 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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 "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" - +#include #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); QScopedPointer container(new MockContainer()); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container.data()); QCOMPARE(shape2->parent(), container.data()); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container.data()); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, vc, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, vc, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); } void TestShapePainting::testPaintHiddenShape() { QScopedPointer top(new MockContainer()); MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top.data()); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList &list) : order(list) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override { order.append(this); MockShape::paint(painter, converter, paintcontext); } QList ℴ }; QList order; { QScopedPointer top(new MockContainer()); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); QScopedPointer bottom(new MockContainer()); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top.data()); manager.addShape(bottom.data()); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); } order.clear(); { QScopedPointer root(new MockContainer()); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root.data()); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root.data()); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); } } #include #include #include #include #include "kis_debug.h" void TestShapePainting::testGroupUngroup() { QScopedPointer shapesFakeLayer(new MockContainer); MockShape *shape1(new MockShape()); MockShape *shape2(new MockShape()); shape1->setName("shape1"); shape2->setName("shape2"); shape1->setParent(shapesFakeLayer.data()); shape2->setParent(shapesFakeLayer.data()); QList groupedShapes = {shape1, shape2}; MockShapeController controller; MockCanvas canvas(&controller); KoShapeManager *manager = canvas.shapeManager(); controller.addShape(shape1); controller.addShape(shape2); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); painter.setClipRect(image.rect()); KoViewConverter vc; for (int i = 0; i < 3; i++) { KoShapeGroup *group = new KoShapeGroup(); group->setParent(shapesFakeLayer.data()); { group->setName("group"); KUndo2Command groupingCommand; canvas.shapeController()->addShapeDirect(group, 0, &groupingCommand); new KoShapeGroupCommand(group, groupedShapes, true, &groupingCommand); groupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 1); QCOMPARE(shape2->paintedCount, 2 * i + 1); QCOMPARE(manager->shapes().size(), 3); } { KUndo2Command ungroupingCommand; new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); canvas.shapeController()->removeShape(group, &ungroupingCommand); ungroupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 2); QCOMPARE(shape2->paintedCount, 2 * i + 2); QCOMPARE(manager->shapes().size(), 2); } group->setParent(0); } } - -QTEST_MAIN(TestShapePainting) +KISTEST_MAIN(TestShapePainting) diff --git a/libs/flake/tests/TestSnapStrategy.cpp b/libs/flake/tests/TestSnapStrategy.cpp index a934a7a4b8..aff19e8fc2 100644 --- a/libs/flake/tests/TestSnapStrategy.cpp +++ b/libs/flake/tests/TestSnapStrategy.cpp @@ -1,840 +1,840 @@ /* Copyright (C) 2012 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "TestSnapStrategy.h" #include #include "KoSnapStrategy.h" #include "KoPathShape.h" #include "KoSnapProxy.h" #include "KoShapeBasedDocumentBase.h" #include "MockShapes.h" #include "KoPathPoint.h" #include "KoViewConverter.h" - +#include //#include #include void TestSnapStrategy::testOrthogonalSnap() { //Test case one - expected not to snap OrthogonalSnapStrategy toTest; const QPointF paramMousePosition; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); //the shapeManager() function of this will be called KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxy(&aKoSnapGuide); //param proxy will have no shapes hence it will not snap qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePosition, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - makes sure the there are shapes in the fakeShapeControllerBase thus it should snap OrthogonalSnapStrategy toTestTwo; //paramMousePosition must be within paramSnapDistance of the points in firstSnapPointList const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; //This call will be made on the paramProxy: proxy->pointsFromShape(shape) which in turn //will make this call shape->snapData().snapPoints(); so the shapes have to have snapPoints //In order to have snapPoints we have to use the call //shape->snapData().setSnapPoints() for each fakeShape, where we send in a const //QList &snapPoints in order to have snapPoints to iterate - which is the only //way to change the value of minHorzDist and minVertDist in KoSnapStrategy.cpp so it //differs from HUGE_VAL - i.e. gives us the true value for the snap function. //creating the lists of points //example QList pts; pts.push_back(QPointF(0.2, 0.3)); pts.push_back(QPointF(0.5, 0.7)); MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); //the shapeManager() function of this will be called KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //param proxy will have shapes now //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; bool didSnapTwo = toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testNodeSnap() { //Test case one - expected to not snap NodeSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Test case two - exercising the branches by putting a shape and snap points into the ShapeManager NodeSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testExtensionSnap() { //bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) ExtensionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - testing the snap by providing ShapeManager with a shape that has snap points and a path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testIntersectionSnap() { //Testing so it does not work without a path IntersectionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Exercising the working snap by providing the shape manager with three path shapes //In order for this test to work the shapeManager has to have more than one fakeShape in it //(requirement in QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes) //And both shapes have to be not-visible IntersectionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; QList firstSnapPointList; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); //pathShapeOne.snapData().setSnapPoints(firstSnapPointList); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; QList secondSnapPointList; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); //pathShapeTwo.snapData().setSnapPoints(secondSnapPointList); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; QList thirdSnapPointList; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! ShapeManager->remove(&pathShapeOne); ShapeManager->remove(&pathShapeTwo); ShapeManager->remove(&pathShapeThree); } void TestSnapStrategy::testGridSnap() { //This test is the default case - meant to fail since the grid of the SnapGuide is not set GridSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //This test tests the snapping by providing the SnapGuide with a grid to snap against GridSnapStrategy toTestTwo; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testBoundingBoxSnap() { //Tests so the snap does not work when there is no shape with a path BoundingBoxSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //tests the snap by providing three path shapes to the shape manager BoundingBoxSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; QList firstSnapPointList; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; QList secondSnapPointList; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; QList thirdSnapPointList; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! ShapeManager->remove(&pathShapeOne); ShapeManager->remove(&pathShapeTwo); ShapeManager->remove(&pathShapeThree); } void TestSnapStrategy::testLineGuideSnap() { // KoGuides data has been moved into Krita // // //Testing so the snap does not work without horizontal and vertial lines // LineGuideSnapStrategy toTest; // const QPointF paramMousePos; // MockShapeController fakeShapeControllerBase; // MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); // KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); // KoSnapProxy paramProxy(&aKoSnapGuide); // qreal paramSnapDistance = 0; // bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); // QVERIFY(!didSnap); // //Test case that covers the path of the snap by providing horizontal and vertical lines for the GuidesData // LineGuideSnapStrategy toTestTwo; // const QPointF paramMousePosTwo; // MockShapeController fakeShapeControllerBaseTwo; // MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); // KoGuidesData guidesData; // QList horzLines; // horzLines.push_back(2); // horzLines.push_back(3); // horzLines.push_back(4); // horzLines.push_back(5); // QList vertLines; // vertLines.push_back(1); // vertLines.push_back(2); // vertLines.push_back(3); // vertLines.push_back(4); // guidesData.setHorizontalGuideLines(horzLines); // guidesData.setVerticalGuideLines(vertLines); // fakeKoCanvasBaseTwo.setGuidesData(&guidesData); // qreal paramSnapDistanceTwo = 8; // KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); // KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); // bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); // QVERIFY(didSnapTwo); } void TestSnapStrategy::testOrhogonalDecoration() { //Making sure the decoration is created but is empty OrthogonalSnapStrategy toTestTwo; const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter irrelevantParameter; QPainterPath resultingDecoration = toTestTwo.decoration(irrelevantParameter); QVERIFY( resultingDecoration.isEmpty() ); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testNodeDecoration() { //Tests so the decoration returns a rect which is inside the "standard outer rect" NodeSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5, -5.5, 11, 11); QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testExtensionDecoration() { //Tests the decoration is exercised by providing it with path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); const KoViewConverter aConverter; QPainterPath resultingDecoration = toTestTwo.decoration(aConverter); QPointF resultDecorationLastPoint = resultingDecoration.currentPosition(); QVERIFY( resultDecorationLastPoint == QPointF(0,2) ); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testIntersectionDecoration() { //Tests the decoration by making sure that the returned rect is within the "standard outer rect" IntersectionSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5,-5.5,11,11); //std outer rect QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testGridDecoration() { //Tests the decoration by making sure the path returned has the calculated endpoint GridSnapStrategy toTest; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(40, 56); //the snapped position is 40, 56 because horz 10 - so 40 is right on the gridline, and 56 because 7*8 = 56 which is within 8 of 60 QPointF originalEndPoint(snappedPos + QPointF(0, unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testBoundingBoxDecoration() { //tests the decoration by making sure the returned path has the pre-calculated end point BoundingBoxSnapStrategy toTest; KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(0,0); QPointF originalEndPoint(snappedPos + QPointF(unzoomedSize.width(), -unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testLineGuideDecoration() { // KoGuides data has been moved into Krita // // //tests the decoration by making sure there are horizontal and vertical lines in the guidesData // LineGuideSnapStrategy toTest; // const QPointF paramMousePosTwo; // MockShapeController fakeShapeControllerBaseTwo; // MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); // KoGuidesData guidesData; // //firstSnapPointList.push_back( // QList horzLines; // horzLines.push_back(2); // horzLines.push_back(3); // horzLines.push_back(4); // horzLines.push_back(5); // QList vertLines; // vertLines.push_back(1); // vertLines.push_back(2); // vertLines.push_back(3); // vertLines.push_back(4); // guidesData.setHorizontalGuideLines(horzLines); // guidesData.setVerticalGuideLines(vertLines); // fakeKoCanvasBaseTwo.setGuidesData(&guidesData); // qreal paramSnapDistanceTwo = 8; // KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); // KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); // toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); // KoViewConverter parameterConverter; // QSizeF unzoomedSize = parameterConverter.viewToDocument(QSizeF(5, 5)); // QPointF snappedPos(1,2); // QPointF originalEndPointOne(snappedPos + QPointF(unzoomedSize.width(), 0)); // QPointF originalEndPointTwo(snappedPos + QPointF(0, unzoomedSize.height())); // QPainterPath resultingDecoration = toTest.decoration(parameterConverter); // QVERIFY( (resultingDecoration.currentPosition() == originalEndPointOne) || (resultingDecoration.currentPosition() == originalEndPointTwo ) ); } void TestSnapStrategy::testSquareDistance() { //tests that it does not work without setting the points OrthogonalSnapStrategy toTest; QPointF p1; QPointF p2; qreal resultingRealOne = toTest.squareDistance(p1, p2); QVERIFY(resultingRealOne == 0); //tests that the returned value is as expected for positive values OrthogonalSnapStrategy toTestTwo; QPointF p1_2(2,2); QPointF p2_2(1,1); qreal resultingRealTwo = toTestTwo.squareDistance(p1_2, p2_2); QVERIFY(resultingRealTwo == 2); //tests that the returned value is as expected for positive and negative values OrthogonalSnapStrategy toTestThree; QPointF p1_3(2,2); QPointF p2_3(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_3, p2_3); QVERIFY(resultingRealThree == 32); //tests that the returned value is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_4(2,2); QPointF p2_4(2,2); qreal resultingRealFour = toTestFour.squareDistance(p1_4, p2_4); QVERIFY(resultingRealFour == 0); } void TestSnapStrategy::testScalarProduct() { //Tests so the scalarProduct cannot be calculated unless the points are set OrthogonalSnapStrategy toTest; QPointF p1_5; QPointF p2_5; qreal resultingRealOne = toTest.squareDistance(p1_5, p2_5); QVERIFY(resultingRealOne == 0 ); //tests that the product is correctly calculated for positive point values OrthogonalSnapStrategy toTestTwo; QPointF p1_6(2,2); QPointF p2_6(3,3); qreal resultingRealTwo = toTestTwo.squareDistance(p1_6, p2_6); QVERIFY(resultingRealTwo == 2 ); //tests that the product is correctly calculated for positive and negative point values OrthogonalSnapStrategy toTestThree; QPointF p1_7(2,2); QPointF p2_7(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_7, p2_7); QVERIFY(resultingRealThree == 32); //tests so the product is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_8(1,1); QPointF p2_8(1,1); qreal resultingRealFour = toTestFour.squareDistance(p1_8, p2_8); QVERIFY(resultingRealFour == 0); //tests so there is nothing fishy when using origo OrthogonalSnapStrategy toTestFive; QPointF p1_9(1,1); QPointF p2_9(0,0); qreal resultingRealFive = toTestFive.squareDistance(p1_9, p2_9); QVERIFY(resultingRealFive == 2); } //------------------------------------------------------------------ void TestSnapStrategy::testSnapToExtension() { /* toTest.snapToExtension(paramPosition, ¶mPoint, paramMatrix); qDebug() << direction << " is the returned direction for this point in TestSnapStrategy::testSnapToExtension()"; QCOMPARE(direction, ); */ } void TestSnapStrategy::testProject() { //tests for positive point values but backwards leaning line ExtensionSnapStrategy toTestOne; qreal toCompWithOne = -1; QPointF lineStart(4,4); QPointF lineEnd(2,2); QPointF comparisonPoint(6,6); qreal resultingRealOne = toTestOne.project(lineStart, lineEnd, comparisonPoint); QCOMPARE(resultingRealOne, toCompWithOne); //testing for for negative point values ExtensionSnapStrategy toTestTwo; qreal toCompWithTwo = -4; QPointF lineStart_2(-2,-2); QPointF lineEnd_2(-4,-4); QPointF comparisonPoint_2(6,6); qreal resultingRealTwo = toTestTwo.project(lineStart_2, lineEnd_2, comparisonPoint_2); QCOMPARE(resultingRealTwo, toCompWithTwo); //testing for negative and positive point values ExtensionSnapStrategy toTestThree; qreal toCompWithThree = (10*(6/sqrt(72.0)) + 10*(6/sqrt(72.0))) / sqrt(72.0); //diffLength = sqrt(72), scalar = (10*(6/sqrt(72)) + 10*(6/sqrt(72))) QPointF lineStart_3(-2,-2); QPointF lineEnd_3(4, 4); QPointF comparisonPoint_3(8,8); qreal resultingRealThree = toTestThree.project(lineStart_3, lineEnd_3, comparisonPoint_3); QCOMPARE(resultingRealThree, toCompWithThree); //Below we test the formula itself for the dot-product by using values we know return t=0.5 //Formula for how to use the t value is: //ProjectionPoint = lineStart*(1-resultingReal) + resultingReal*lineEnd; (this is the formula used in BoundingBoxSnapStrategy::squareDistanceToLine()) //Note: The angle of the line from projection point to comparison point is always 90 degrees ExtensionSnapStrategy toTestFour; qreal toCompWithFour = 0.5; QPointF lineStart_4(2,1); QPointF lineEnd_4(6,3); QPointF comparisonPoint_4(3,4); qreal resultingRealFour = toTestFour.project(lineStart_4, lineEnd_4, comparisonPoint_4); QCOMPARE(resultingRealFour, toCompWithFour); } void TestSnapStrategy::testExtensionDirection() { /* TEST CASE 0 Supposed to return null */ ExtensionSnapStrategy toTestOne; KoPathShape uninitiatedPathShape; KoPathPoint::PointProperties normal = KoPathPoint::Normal; const QPointF initiatedPoint0(0,0); KoPathPoint initiatedPoint(&uninitiatedPathShape, initiatedPoint0, normal); QMatrix initiatedMatrixParam(1,1,1,1,1,1); const QTransform initiatedMatrix(initiatedMatrixParam); QPointF direction2 = toTestOne.extensionDirection( &initiatedPoint, initiatedMatrix); QVERIFY(direction2.isNull()); /* TEST CASE 1 tests a point that: - is the first in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has no previous point = expected returning an empty QPointF */ ExtensionSnapStrategy toTestTwo; QPointF expectedPointTwo(0,0); KoPathShape shapeOne; QPointF firstPoint(0,1); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); shapeOne.moveTo(firstPoint); shapeOne.lineTo(secondPoint); shapeOne.lineTo(thirdPoint); shapeOne.lineTo(fourthPoint); QPointF paramPositionTwo(0,1); KoPathPoint paramPointTwo; paramPointTwo.setPoint(paramPositionTwo); paramPointTwo.setParent(&shapeOne); const QTransform paramTransMatrix(1,2,3,4,5,6); QPointF directionTwo = toTestTwo.extensionDirection( ¶mPointTwo, paramTransMatrix); QCOMPARE(directionTwo, expectedPointTwo); /* TEST CASE 2 tests a point that: - is the second in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has a previous point = expected returning an */ ExtensionSnapStrategy toTestThree; QPointF expectedPointThree(0,0); QPointF paramPositionThree(1,1); KoPathPoint paramPointThree; paramPointThree.setPoint(paramPositionThree); paramPointThree.setParent(&shapeOne); QPointF directionThree = toTestThree.extensionDirection( ¶mPointThree, paramTransMatrix); QCOMPARE(directionThree, expectedPointThree); } void TestSnapStrategy::testSquareDistanceToLine() { BoundingBoxSnapStrategy toTestOne; const QPointF lineA(4,1); const QPointF lineB(6,3); const QPointF point(5,8); QPointF pointOnLine(0,0); qreal result = toTestOne.squareDistanceToLine(lineA, lineB, point, pointOnLine); //Should be HUGE_VAL because scalar > diffLength QVERIFY(result == HUGE_VAL); BoundingBoxSnapStrategy toTestTwo; QPointF lineA2(4,4); QPointF lineB2(4,4); QPointF point2(5,8); QPointF pointOnLine2(0,0); qreal result2 = toTestTwo.squareDistanceToLine(lineA2, lineB2, point2, pointOnLine2); //Should be HUGE_VAL because lineA2 == lineB2 QVERIFY(result2 == HUGE_VAL); BoundingBoxSnapStrategy toTestThree; QPointF lineA3(6,4); QPointF lineB3(8,6); QPointF point3(2,2); QPointF pointOnLine3(0,0); qreal result3 = toTestThree.squareDistanceToLine(lineA3, lineB3, point3, pointOnLine3); //Should be HUGE_VAL because scalar < 0.0 QVERIFY(result3 == HUGE_VAL); BoundingBoxSnapStrategy toTestFour; QPointF lineA4(2,2); QPointF lineB4(8,6); QPointF point4(3,4); QPointF pointOnLine4(0,0); QPointF diff(6,4); //diff = lineB3 - point3 = 6,4 //diffLength = sqrt(52) //scalar = (1*(6/sqrt(52)) + 2*(4/sqrt(52))); //pointOnLine = lineA + scalar / diffLength * diff; lineA + ((1*(6/sqrt(52)) + 2*(4/sqrt(52))) / sqrt(52)) * 6,4; QPointF distToPointOnLine = (lineA4 + ((1*(6/sqrt(52.0)) + 2*(4/sqrt(52.0))) / sqrt(52.0)) * diff)-point4; qreal toCompWithFour = distToPointOnLine.x()*distToPointOnLine.x()+distToPointOnLine.y()*distToPointOnLine.y(); qreal result4 = toTestFour.squareDistanceToLine(lineA4, lineB4, point4, pointOnLine4); //Normal case with example data QVERIFY(qFuzzyCompare(result4, toCompWithFour)); } -QTEST_MAIN(TestSnapStrategy) +KISTEST_MAIN(TestSnapStrategy) diff --git a/libs/flake/tests/TestSvgParser.cpp b/libs/flake/tests/TestSvgParser.cpp index 1592fae5e7..05b9f2f332 100644 --- a/libs/flake/tests/TestSvgParser.cpp +++ b/libs/flake/tests/TestSvgParser.cpp @@ -1,3218 +1,3219 @@ /* * 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 "TestSvgParser.h" #include #include #include #include "SvgParserTestingUtils.h" #include "../../sdk/tests/qimage_test_util.h" #ifdef USE_ROUND_TRIP #include "SvgWriter.h" #include #include #endif void TestSvgParser::testUnitPx() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5)); QCOMPARE(shape->absoluteTransformation(0), QTransform()); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20)); } void TestSvgParser::testUnitPxResolution() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10)); } void TestSvgParser::testUnitPt() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5)); QCOMPARE(shape->absoluteTransformation(0), QTransform()); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20)); } void TestSvgParser::testUnitIn() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,720,1440), 36)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(72, 72)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(720,1440)); } void TestSvgParser::testUnitPercentInitial() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 80, 80) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10)); } void TestSvgParser::testScalingViewport() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeet1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeet2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeetAlign() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 24) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,12)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,12)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,28)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,28)); } void TestSvgParser::testScalingViewportKeepSlice1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepSlice2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportResolution() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.25, 0.25)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(1,1)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(4,1)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(1,9)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(4,9)); } void TestSvgParser::testScalingViewportPercentInternal() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testParsePreserveAspectRatio() { { SvgUtil::PreserveAspectRatioParser p(" defer xMinYMax meet"); QCOMPARE(p.defer, true); QCOMPARE(p.mode, Qt::KeepAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Max); } { SvgUtil::PreserveAspectRatioParser p(" xMinYMid slice"); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::KeepAspectRatioByExpanding); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle); } { SvgUtil::PreserveAspectRatioParser p(" xmidYMid "); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::KeepAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Middle); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle); } { SvgUtil::PreserveAspectRatioParser p(" NoNe "); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } { SvgUtil::PreserveAspectRatioParser p("defer NoNe "); QCOMPARE(p.defer, true); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } { SvgUtil::PreserveAspectRatioParser p("sweet brown fox jumps over a nice svg file"); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } } #include "parsers/SvgTransformParser.h" void TestSvgParser::testParseTransform() { { QString str("translate(-111.0, 33) translate(-111.0, 33) matrix (1 1 0 0 1, 3), translate(1)" "scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)"); SvgTransformParser p(str); QCOMPARE(p.isValid(), true); } { // forget about one brace QString str("translate(-111.0, 33) translate(-111.0, 33 matrix (1 1 0 0 1, 3), translate(1)" "scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)"); SvgTransformParser p(str); QCOMPARE(p.isValid(), false); } { SvgTransformParser p("translate(100, 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50)); } { SvgTransformParser p("translate(100 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50)); } { SvgTransformParser p("translate(100)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 0)); } { SvgTransformParser p("scale(100, 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromScale(100, 50)); } { SvgTransformParser p("scale(100)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromScale(100, 100)); } { SvgTransformParser p("rotate(90 70 74.0)"); QCOMPARE(p.isValid(), true); QTransform t; t.rotate(90); t = QTransform::fromTranslate(-70, -74) * t * QTransform::fromTranslate(70, 74); qDebug() << ppVar(p.transform()); QCOMPARE(p.transform(), t); } } void TestSvgParser::testScalingViewportTransform() { /** * Note: 'transform' affects all the attributes of the *current* * element, while 'viewBox' affects only the descendants! */ const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(10, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(11,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(5,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(11,18)); } void TestSvgParser::testTransformNesting() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30)); } void TestSvgParser::testTransformNestingGroups() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30)); } void TestSvgParser::testTransformRotation1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(-20,0,20,10), 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(-20,10)); } void TestSvgParser::testTransformRotation2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(5,5,20,10), 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,15)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(25,5)); } void TestSvgParser::testRenderStrokeNone() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_none"); } void TestSvgParser::testRenderStrokeColorName() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorHex3() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorHex6() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorRgbValues() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorRgbPercent() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorCurrent() { const QString data = "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorNonexistentIri() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeWidth() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_width_2"); } void TestSvgParser::testRenderStrokeZeroWidth() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_none"); } void TestSvgParser::testRenderStrokeOpacity() { const QString data = "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("stroke_blue_0_3_opacity"); } void TestSvgParser::testRenderStrokeJointRound() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_join_round"); } void TestSvgParser::testRenderStrokeLinecap() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_linecap_round"); } void TestSvgParser::testRenderStrokeMiterLimit() { // TODO:seems like doesn't work!! qWarning() << "WARNING: Miter limit test is skipped!!!"; return; const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_miter_limit"); } void TestSvgParser::testRenderStrokeDashArrayEven() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_even"); } void TestSvgParser::testRenderStrokeDashArrayEvenOffset() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_even_offset"); } void TestSvgParser::testRenderStrokeDashArrayOdd() { // SVG 1.1: if the dasharray is odd, repeat it const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_odd"); } void TestSvgParser::testRenderStrokeDashArrayRelative() { // SVG 1.1: relative to view box // (40 x 50) * sqrt(2) => dash length = 5 px const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_relative"); } void TestSvgParser::testRenderFillDefault() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_black"); } void TestSvgParser::testRenderFillRuleNonZero() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_non_zero"); } void TestSvgParser::testRenderFillRuleEvenOdd() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_even_odd"); } void TestSvgParser::testRenderFillOpacity() { const QString data = "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_opacity_0_3"); } void TestSvgParser::testRenderDisplayAttribute() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } void TestSvgParser::testRenderVisibilityAttribute() { { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), true); } { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } } void TestSvgParser::testRenderVisibilityInheritance() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(false), true); QCOMPARE(shape->isVisible(true), false); } void TestSvgParser::testRenderDisplayInheritance() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(false), true); QEXPECT_FAIL("", "TODO: Fix 'display' attribute not to be inherited in shapes hierarchy!", Continue); QCOMPARE(shape->isVisible(true), true); } void TestSvgParser::testRenderStrokeWithInlineStyle() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_width_2"); } void TestSvgParser::testIccColor() { const QString data = "" "" " " " " " " "" ""; SvgRenderTester t (data); int numFetches = 0; t.parser.setFileFetcher( [&numFetches](const QString &name) { numFetches++; const QString fileName = TestUtil::fetchDataFileLazy(name); QFile file(fileName); KIS_ASSERT(file.exists()); file.open(QIODevice::ReadOnly); return file.readAll(); }); t.test_standard_30px_72ppi("stroke_blue_width_2"); QCOMPARE(numFetches, 1); } void TestSvgParser::testRenderFillLinearGradientRelativePercent() { const QString data = "" "" " " " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientRelativePortion() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientStopPortion() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientTransform() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_vertical"); } void TestSvgParser::testRenderFillLinearGradientTransformUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_vertical_in_user"); } void TestSvgParser::testRenderFillLinearGradientRotatedShape() { // DK: I'm not sure I fully understand if it is a correct transformation, // but inkscape opens the file in the same way... const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_shape_rotated", false); } void TestSvgParser::testRenderFillLinearGradientRotatedShapeUserCoord() { // DK: I'm not sure I fully understand if it is a correct transformation, // but inkscape opens the file in the same way... const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_shape_rotated_in_user", false); } void TestSvgParser::testRenderFillRadialGradient() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_gradient_radial"); } void TestSvgParser::testRenderFillRadialGradientUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_gradient_radial_in_user"); } void TestSvgParser::testRenderFillLinearGradientUserCoordPercent() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderStrokeLinearGradient() { const QString data = "" "" " " " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_gradient_dashed"); } QTransform rotateTransform(qreal degree, const QPointF ¢er) { QTransform rotate; rotate.rotate(degree); return QTransform::fromTranslate(-center.x(), -center.y()) * rotate * QTransform::fromTranslate(center.x(), center.y()); } QTransform viewTransform(const QRectF &src, const QRectF &dst) { return QTransform::fromTranslate(-src.x(), -src.y()) * QTransform::fromScale(dst.width() / src.width(), dst.height() / src.height()) * QTransform::fromTranslate(dst.x(), dst.y()); } QPainterPath bakeShape(const QPainterPath &path, const QTransform &bakeTransform, bool contentIsObb = false, const QRectF &shapeBoundingRect = QRectF(), bool contentIsViewBox = false, const QRectF &viewBoxRect= QRectF(), const QRectF &refRect = QRectF()) { const QTransform relativeToShape(shapeBoundingRect.width(), 0, 0, shapeBoundingRect.height(), shapeBoundingRect.x(), shapeBoundingRect.y()); QTransform newTransform = bakeTransform; if (contentIsObb) { newTransform = relativeToShape * newTransform; } if (contentIsViewBox) { newTransform = viewTransform(viewBoxRect, refRect) * newTransform; } return newTransform.map(path); } #include void renderBakedPath(QPainter &painter, const QPainterPath &bakedFillPath, const QTransform &bakedTransform, const QRect &shapeOutline, const QTransform &shapeTransform, const QRectF &referenceRect, bool contentIsObb, const QRectF &bakedShapeBoundingRect, bool referenceIsObb, const QTransform &patternTransform, QImage *stampResult) { painter.setTransform(QTransform()); painter.setPen(Qt::NoPen); QPainterPath shapeOutlinePath; shapeOutlinePath.addRect(shapeOutline); KoBakedShapeRenderer renderer( shapeOutlinePath, shapeTransform, bakedTransform, referenceRect, contentIsObb, bakedShapeBoundingRect, referenceIsObb, patternTransform); QPainter *patchPainter = renderer.bakeShapePainter(); patchPainter->fillPath(bakedFillPath, Qt::blue); patchPainter->end(); renderer.renderShape(painter); if (stampResult) { *stampResult = renderer.patchImage(); } } void TestSvgParser::testManualRenderPattern_ContentUser_RefObb() { const QRectF referenceRect(0, 0, 1.0, 0.5); QPainterPath fillPath; fillPath.addRect(QRect(2, 2, 6, 6)); fillPath.addRect(QRect(8, 4, 3, 2)); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform); QRect shape1OutlineRect(0,0,10,20); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, QRectF(), true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, QRectF(), true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentObb_RefObb() { const QRectF referenceRect(0.3, 0.3, 0.4, 0.4); QPainterPath fillPath; fillPath.addRect(QRectF(0.4, 0.4, 0.2, 0.2)); fillPath.addRect(QRectF(0.6, 0.5, 0.1, 0.1)); fillPath.addRect(QRectF(0.3, 0.4, 0.1, 0.1)); const QRect bakedShapeRect(2,2,10,10); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); // Round trip to the same shape fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, bakedShapeRect, bakedTransform, referenceRect, true, bakedShapeRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill1")); // Move to a different shape QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, bakedShapeRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentUser_RefUser() { const QRectF referenceRect(5, 2, 8, 8); QPainterPath fillPath; fillPath.addRect(QRect(2, 2, 6, 6)); fillPath.addRect(QRect(8, 4, 3, 2)); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform); QRect shape1OutlineRect(0,0,10,20); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, QRectF(), false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, QRectF(), false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill2")); } void TestSvgParser::testManualRenderPattern_ContentObb_RefObb_Transform_Rotate() { const QRectF referenceRect(0.0, 0.0, 0.4, 0.2); QPainterPath fillPath; fillPath.addRect(QRectF(0.0, 0.0, 0.5, 0.1)); fillPath.addRect(QRectF(0.0, 0.1, 0.1, 0.1)); const QRect bakedShapeRect(2,1,10,10); QTransform bakedTransform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(10,10); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); QTransform patternTransform; patternTransform.rotate(90); patternTransform = patternTransform * QTransform::fromTranslate(0.5, 0.0); // Round trip to the same shape fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, bakedShapeRect, bakedTransform, referenceRect, true, bakedShapeRect, true, patternTransform, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, bakedShapeRect, true, patternTransform, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill2")); } void TestSvgParser::testManualRenderPattern_ContentView_RefObb() { const QRectF referenceRect(0, 0, 0.5, 1.0/3.0); const QRectF viewRect(10,10,60,90); QPainterPath fillPath; fillPath.addRect(QRect(30, 10, 20, 60)); fillPath.addRect(QRect(50, 40, 20, 30)); QRect shape1OutlineRect(10,20,40,120); QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, shape1OutlineRect, true, viewRect, referenceRect); QImage stampResult; QImage fillResult(QSize(220,160), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, true, shape1OutlineRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill1")); QRect shape2OutlineRect(20,10,60,90); QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, shape1OutlineRect, true, rotateTransform(90, QPointF(0, 1.0 / 3.0)), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentView_RefUser() { const QRectF referenceRect(60, 0, 30, 20); const QRectF viewRect(10,10,60,90); QPainterPath fillPath; fillPath.addRect(QRect(30, 10, 20, 60)); fillPath.addRect(QRect(50, 40, 20, 30)); QRect shape1OutlineRect(10,20,40,120); QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, false, shape1OutlineRect, true, viewRect, referenceRect); QImage stampResult; QImage fillResult(QSize(220,160), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, shape1OutlineRect, false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill1")); QRect shape2OutlineRect(20,10,60,90); QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50); QTransform patternTransform2 = rotateTransform(90, QPointF()) * QTransform::fromTranslate(40, 10); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, shape1OutlineRect, false, patternTransform2, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill2")); } void TestSvgParser::testRenderPattern_r_User_c_User() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_InfiniteRecursionWhenInherited() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base_black", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_View() { const QString data = "" "" " " " " " " // y is changed to 39 from 40 to fix a rounding issue! " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_Obb() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_View_Rotated() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_rotated", false, QSize(220, 160)); } void TestSvgParser::testRenderPattern_r_Obb_c_View_Rotated() { /** * This test case differs from any application existent in the world :( * * Chrome and Firefox premultiply the patternTransform instead of doing post- * multiplication. Photoshop forgets to multiply the reference rect on it. * * So... */ const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_rotated_odd", false, QSize(220, 160)); } #include #include #include #include void TestSvgParser::testKoClipPathRendering() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); QPainterPath path2; path2.addRect(QRect(10,10,15,15)); QPainterPath clipPath1; clipPath1.addRect(QRect(10, 0, 10, 30)); QPainterPath clipPath2; clipPath2.moveTo(0,7); clipPath2.lineTo(30,7); clipPath2.lineTo(15,30); clipPath2.lineTo(0,7); QScopedPointer shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); QScopedPointer shape2(KoPathShape::createShapeFromPainterPath(path2)); shape2->setBackground(QSharedPointer(new KoColorBackground(Qt::red))); QScopedPointer clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1)); KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse); koClipPath1->setClipRule(Qt::WindingFill); shape1->setClipPath(koClipPath1); QScopedPointer group(new KoShapeGroup()); { QList shapes({shape1.take(), shape2.take()}); KoShapeGroupCommand cmd(group.data(), shapes, false); cmd.redo(); } QScopedPointer clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2)); KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::UserSpaceOnUse); koClipPath2->setClipRule(Qt::WindingFill); group->setClipPath(koClipPath2); SvgRenderTester::testRender(group.take(), "load", "clip_render_test", QSize(30,30)); } void TestSvgParser::testKoClipPathRelativeRendering() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); QPainterPath path2; path2.addRect(QRect(10,10,15,15)); QPainterPath clipPath1; clipPath1.addRect(QRect(10, 0, 10, 30)); QPainterPath clipPath2; clipPath2.moveTo(0,0); clipPath2.lineTo(1,0); clipPath2.lineTo(0.5,1); clipPath2.lineTo(0,0); QScopedPointer shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); QScopedPointer shape2(KoPathShape::createShapeFromPainterPath(path2)); shape2->setBackground(QSharedPointer(new KoColorBackground(Qt::red))); QScopedPointer clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1)); KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse); koClipPath1->setClipRule(Qt::WindingFill); shape1->setClipPath(koClipPath1); QScopedPointer group(new KoShapeGroup()); { QList shapes({shape1.take(), shape2.take()}); KoShapeGroupCommand cmd(group.data(), shapes, false); cmd.redo(); } QScopedPointer clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2)); KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::ObjectBoundingBox); koClipPath2->setClipRule(Qt::WindingFill); group->setClipPath(koClipPath2); SvgRenderTester::testRender(group.take(), "load", "relative_clip_render_test", QSize(30,30)); } void TestSvgParser::testRenderClipPath_User() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_render_test", false); } void TestSvgParser::testRenderClipPath_Obb() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("relative_clip_render_test", false); } void TestSvgParser::testRenderClipPath_Obb_Transform() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_render_test_rotated", false); } void TestSvgParser::testRenderClipMask_Obb() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } void TestSvgParser::testRenderClipMask_User_Clip_Obb() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } void TestSvgParser::testRenderClipMask_User_Clip_User() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } QByteArray fileFetcherFunc(const QString &name) { const QString fileName = TestUtil::fetchDataFileLazy(name); QFile file(fileName); KIS_ASSERT(file.exists()); file.open(QIODevice::ReadOnly); return file.readAll(); } void TestSvgParser::testRenderImage_AspectDefault() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_default", false); } void TestSvgParser::testRenderImage_AspectNone() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); + t.setFuzzyThreshold(5); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_none", false); } void TestSvgParser::testRenderImage_AspectMeet() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_meet", false); } void TestSvgParser::testRectShapeRoundUniformX() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_5", false); } void TestSvgParser::testRectShapeRoundUniformY() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_5", false); } void TestSvgParser::testRectShapeRoundXY() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_10", false); } void TestSvgParser::testRectShapeRoundXYOverflow() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_10", false); } void TestSvgParser::testCircleShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("circle", false); } void TestSvgParser::testEllipseShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("ellipse", false); } void TestSvgParser::testLineShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("line", false); } void TestSvgParser::testPolylineShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polyline", false); } void TestSvgParser::testPolygonShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polygon", false); } void TestSvgParser::testPathShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polygon", false); } void TestSvgParser::testDefsHidden() { const QString data = "" "" " " " " " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("test_defs_hidden", false); } void TestSvgParser::testDefsUseInheritance() { const QString data = "" "" " " " " " " "" /** * NOTES: * 1) width/height attributes for are not implemented yet * 2) x and y are summed up * 3) stroke="white" is overridden by the original templated object * 4) fill="green" attribute from is not inherited */ "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("defs_use_inheritance", false); } void TestSvgParser::testUseWithoutDefs() { const QString data = "" // technical rect for rendering "" "" " " "" /** * NOTES: * 1) width/height attributes for are not implemented yet * 2) x and y are summed up * 3) stroke="white" is overridden by the original templated object * 4) fill="green" attribute from is not inherited */ "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("use_without_defs", false); } void TestSvgParser::testMarkersAutoOrientation() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers", false); } void TestSvgParser::testMarkersAutoOrientationScaled() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_scaled", false); } void TestSvgParser::testMarkersAutoOrientationScaledUserCoordinates() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_user_coordinates", false); } void TestSvgParser::testMarkersCustomOrientation() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_custom_orientation", false); } void TestSvgParser::testMarkersDifferent() { const QString data = "" "" " " " " " " "" "" " " " " " " "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_different", false); } void TestSvgParser::testMarkersFillAsShape() { const QString data = "" "" " " " " " " " " " " " " " " " " " " "" "" //" d=\"M5,15 L25,15\"/>" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_scaled_fill_as_shape", false); } void TestSvgParser::testMarkersOnClosedPath() { const QString data = "" "" " " " " " " "" "" " " " " " " "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_on_closed_path", false); } void TestSvgParser::testGradientRecoveringTrasnform() { // used for experimenting purposes only! QImage image(100,100,QImage::Format_ARGB32); image.fill(0); QPainter painter(&image); painter.setPen(QPen(Qt::black, 0)); QLinearGradient gradient(0, 0.5, 1, 0.5); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); //QLinearGradient gradient(0, 50, 100, 50); //gradient.setCoordinateMode(QGradient::LogicalMode); gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(1.0, Qt::blue); QTransform gradientTrasnform; gradientTrasnform.shear(0.2, 0); { QBrush brush(gradient); brush.setTransform(gradientTrasnform); painter.setBrush(brush); } QRect mainShape(3,3,94,94); painter.drawRect(mainShape); QTransform gradientToUser(mainShape.width(), 0, 0, mainShape.height(), mainShape.x(), mainShape.y()); QRect smallShape(0,0,20,20); QTransform smallShapeTransform; { smallShapeTransform = QTransform::fromTranslate(-smallShape.center().x(), -smallShape.center().y()); QTransform r; r.rotate(90); smallShapeTransform *= r; smallShapeTransform *= QTransform::fromTranslate(mainShape.center().x(), mainShape.center().y()); } { gradient.setCoordinateMode(QGradient::LogicalMode); QBrush brush(gradient); brush.setTransform(gradientTrasnform * gradientToUser * smallShapeTransform.inverted()); painter.setBrush(brush); painter.setPen(Qt::NoPen); } painter.setTransform(smallShapeTransform); painter.drawRect(smallShape); //image.save("gradient_recovering_transform.png"); } void TestSvgParser::testMarkersAngularUnits() { const QString data = "" // start "" " " " " " " "" // end "" " " " " " " "" // mid "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_angular_units", false); } #include "KoParameterShape.h" void TestSvgParser::testSodipodiArcShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_closed_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testSodipodiArcShapeOpen() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_open_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testKritaChordShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_chord_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testSodipodiChordShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_chord_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } QTEST_MAIN(TestSvgParser) diff --git a/libs/flake/tests/TestSvgParser.h b/libs/flake/tests/TestSvgParser.h index 34351c3422..8fd39dcf34 100644 --- a/libs/flake/tests/TestSvgParser.h +++ b/libs/flake/tests/TestSvgParser.h @@ -1,173 +1,175 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTSVGPARSER_H #define TESTSVGPARSER_H #include class TestSvgParser : public QObject { Q_OBJECT private Q_SLOTS: void testUnitPx(); void testUnitPxResolution(); void testUnitPt(); void testUnitIn(); void testUnitPercentInitial(); void testScalingViewport(); void testScalingViewportKeepMeet1(); void testScalingViewportKeepMeet2(); void testScalingViewportKeepMeetAlign(); void testScalingViewportKeepSlice1(); void testScalingViewportKeepSlice2(); void testScalingViewportResolution(); void testScalingViewportPercentInternal(); void testParsePreserveAspectRatio(); void testParseTransform(); void testScalingViewportTransform(); void testTransformNesting(); void testTransformNestingGroups(); void testTransformRotation1(); void testTransformRotation2(); void testRenderStrokeNone(); void testRenderStrokeColorName(); void testRenderStrokeColorHex3(); void testRenderStrokeColorHex6(); void testRenderStrokeColorRgbValues(); void testRenderStrokeColorRgbPercent(); void testRenderStrokeColorCurrent(); void testRenderStrokeColorNonexistentIri(); void testRenderStrokeWidth(); void testRenderStrokeZeroWidth(); void testRenderStrokeOpacity(); void testRenderStrokeJointRound(); void testRenderStrokeLinecap(); void testRenderStrokeMiterLimit(); void testRenderStrokeDashArrayEven(); void testRenderStrokeDashArrayEvenOffset(); void testRenderStrokeDashArrayOdd(); void testRenderStrokeDashArrayRelative(); void testRenderFillDefault(); void testRenderFillRuleNonZero(); void testRenderFillRuleEvenOdd(); void testRenderFillOpacity(); void testRenderDisplayAttribute(); void testRenderVisibilityAttribute(); void testRenderVisibilityInheritance(); void testRenderDisplayInheritance(); void testRenderStrokeWithInlineStyle(); void testIccColor(); void testRenderFillLinearGradientRelativePercent(); void testRenderFillLinearGradientRelativePortion(); void testRenderFillLinearGradientUserCoord(); void testRenderFillLinearGradientStopPortion(); void testRenderFillLinearGradientTransform(); void testRenderFillLinearGradientTransformUserCoord(); void testRenderFillLinearGradientRotatedShape(); void testRenderFillLinearGradientRotatedShapeUserCoord(); void testRenderFillRadialGradient(); void testRenderFillRadialGradientUserCoord(); void testRenderFillLinearGradientUserCoordPercent(); void testRenderStrokeLinearGradient(); void testManualRenderPattern_ContentUser_RefObb(); void testManualRenderPattern_ContentObb_RefObb(); void testManualRenderPattern_ContentUser_RefUser(); void testManualRenderPattern_ContentObb_RefObb_Transform_Rotate(); void testManualRenderPattern_ContentView_RefObb(); void testManualRenderPattern_ContentView_RefUser(); void testRenderPattern_r_User_c_User(); void testRenderPattern_InfiniteRecursionWhenInherited(); void testRenderPattern_r_User_c_View(); void testRenderPattern_r_User_c_Obb(); void testRenderPattern_r_User_c_View_Rotated(); void testRenderPattern_r_Obb_c_View_Rotated(); void testKoClipPathRendering(); void testKoClipPathRelativeRendering(); void testRenderClipPath_User(); void testRenderClipPath_Obb(); void testRenderClipPath_Obb_Transform(); void testRenderClipMask_Obb(); void testRenderClipMask_User_Clip_Obb(); void testRenderClipMask_User_Clip_User(); void testRenderImage_AspectDefault(); void testRenderImage_AspectNone(); void testRenderImage_AspectMeet(); void testRectShapeRoundUniformX(); void testRectShapeRoundUniformY(); void testRectShapeRoundXY(); void testRectShapeRoundXYOverflow(); void testCircleShape(); void testEllipseShape(); void testLineShape(); void testPolylineShape(); void testPolygonShape(); void testPathShape(); void testDefsHidden(); void testDefsUseInheritance(); void testUseWithoutDefs(); void testMarkersAutoOrientation(); void testMarkersAutoOrientationScaled(); void testMarkersAutoOrientationScaledUserCoordinates(); void testMarkersCustomOrientation(); void testMarkersDifferent(); - void testMarkersFillAsShape(); - void testGradientRecoveringTrasnform(); void testMarkersOnClosedPath(); void testMarkersAngularUnits(); void testSodipodiArcShape(); void testSodipodiArcShapeOpen(); void testKritaChordShape(); void testSodipodiChordShape(); + + void testMarkersFillAsShape(); +private: + }; #endif // TESTSVGPARSER_H diff --git a/libs/flake/tests/TestSvgText.cpp b/libs/flake/tests/TestSvgText.cpp index 1e42bdbc5d..9d11c16d00 100644 --- a/libs/flake/tests/TestSvgText.cpp +++ b/libs/flake/tests/TestSvgText.cpp @@ -1,1207 +1,1220 @@ /* * 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 "TestSvgText.h" #include #include "SvgParserTestingUtils.h" #include #include #include "KoSvgTextShapeMarkupConverter.h" #include #include #include void addProp(SvgLoadingContext &context, KoSvgTextProperties &props, const QString &attribute, const QString &value, KoSvgTextProperties::PropertyId id, int newValue) { props.parseSvgTextAttribute(context, attribute, value); if (props.property(id).toInt() != newValue) { qDebug() << "Failed to load the property:"; qDebug() << ppVar(attribute) << ppVar(value); qDebug() << ppVar(newValue); qDebug() << ppVar(props.property(id)); QFAIL("Fail :("); } } void addProp(SvgLoadingContext &context, KoSvgTextProperties &props, const QString &attribute, const QString &value, KoSvgTextProperties::PropertyId id, KoSvgText::AutoValue newValue) { props.parseSvgTextAttribute(context, attribute, value); if (props.property(id).value() != newValue) { qDebug() << "Failed to load the property:"; qDebug() << ppVar(attribute) << ppVar(value); qDebug() << ppVar(newValue); qDebug() << ppVar(props.property(id)); QFAIL("Fail :("); } QCOMPARE(props.property(id), QVariant::fromValue(newValue)); } void addProp(SvgLoadingContext &context, KoSvgTextProperties &props, const QString &attribute, const QString &value, KoSvgTextProperties::PropertyId id, qreal newValue) { props.parseSvgTextAttribute(context, attribute, value); if (props.property(id).toReal() != newValue) { qDebug() << "Failed to load the property:"; qDebug() << ppVar(attribute) << ppVar(value); qDebug() << ppVar(newValue); qDebug() << ppVar(props.property(id)); QFAIL("Fail :("); } } void TestSvgText::testTextProperties() { KoDocumentResourceManager resourceManager; SvgLoadingContext context(&resourceManager); context.pushGraphicsContext(); KoSvgTextProperties props; addProp(context, props, "writing-mode", "tb-rl", KoSvgTextProperties::WritingModeId, KoSvgText::TopToBottom); addProp(context, props, "writing-mode", "rl", KoSvgTextProperties::WritingModeId, KoSvgText::RightToLeft); addProp(context, props, "glyph-orientation-vertical", "auto", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue()); addProp(context, props, "glyph-orientation-vertical", "0", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(0)); addProp(context, props, "glyph-orientation-vertical", "90", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2)); addProp(context, props, "glyph-orientation-vertical", "95", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2)); addProp(context, props, "glyph-orientation-vertical", "175", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI)); addProp(context, props, "glyph-orientation-vertical", "280", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(3 * M_PI_2)); addProp(context, props, "glyph-orientation-vertical", "350", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(0)); addProp(context, props, "glyph-orientation-vertical", "105", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2)); addProp(context, props, "glyph-orientation-horizontal", "0", KoSvgTextProperties::GlyphOrientationHorizontalId, 0.0); addProp(context, props, "glyph-orientation-horizontal", "90", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI_2); addProp(context, props, "glyph-orientation-horizontal", "95", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI_2); addProp(context, props, "glyph-orientation-horizontal", "175", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI); addProp(context, props, "glyph-orientation-horizontal", "280", KoSvgTextProperties::GlyphOrientationHorizontalId, 3 * M_PI_2); addProp(context, props, "direction", "rtl", KoSvgTextProperties::WritingModeId, KoSvgText::DirectionRightToLeft); addProp(context, props, "unicode-bidi", "embed", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiEmbed); addProp(context, props, "unicode-bidi", "bidi-override", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiOverride); addProp(context, props, "text-anchor", "middle", KoSvgTextProperties::TextAnchorId, KoSvgText::AnchorMiddle); addProp(context, props, "dominant-baseline", "ideographic", KoSvgTextProperties::DominantBaselineId, KoSvgText::DominantBaselineIdeographic); addProp(context, props, "alignment-baseline", "alphabetic", KoSvgTextProperties::AlignmentBaselineId, KoSvgText::AlignmentBaselineAlphabetic); addProp(context, props, "baseline-shift", "sub", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSub); addProp(context, props, "baseline-shift", "super", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSuper); addProp(context, props, "baseline-shift", "baseline", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftNone); addProp(context, props, "baseline-shift", "10%", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage); QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.1); context.currentGC()->font.setPointSizeF(180); addProp(context, props, "baseline-shift", "36", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage); QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.2); addProp(context, props, "kerning", "auto", KoSvgTextProperties::KerningId, KoSvgText::AutoValue()); addProp(context, props, "kerning", "20", KoSvgTextProperties::KerningId, KoSvgText::AutoValue(20.0)); addProp(context, props, "letter-spacing", "normal", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue()); addProp(context, props, "letter-spacing", "20", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue(20.0)); addProp(context, props, "word-spacing", "normal", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue()); addProp(context, props, "word-spacing", "20", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue(20.0)); } void TestSvgText::testDefaultTextProperties() { KoSvgTextProperties props; QVERIFY(props.isEmpty()); QVERIFY(!props.hasProperty(KoSvgTextProperties::UnicodeBidiId)); QVERIFY(KoSvgTextProperties::defaultProperties().hasProperty(KoSvgTextProperties::UnicodeBidiId)); QCOMPARE(KoSvgTextProperties::defaultProperties().property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal)); props = KoSvgTextProperties::defaultProperties(); QVERIFY(props.hasProperty(KoSvgTextProperties::UnicodeBidiId)); QCOMPARE(props.property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal)); } void TestSvgText::testTextPropertiesDifference() { using namespace KoSvgText; KoSvgTextProperties props; props.setProperty(KoSvgTextProperties::WritingModeId, RightToLeft); props.setProperty(KoSvgTextProperties::DirectionId, DirectionRightToLeft); props.setProperty(KoSvgTextProperties::UnicodeBidiId, BidiEmbed); props.setProperty(KoSvgTextProperties::TextAnchorId, AnchorEnd); props.setProperty(KoSvgTextProperties::DominantBaselineId, DominantBaselineNoChange); props.setProperty(KoSvgTextProperties::AlignmentBaselineId, AlignmentBaselineIdeographic); props.setProperty(KoSvgTextProperties::BaselineShiftModeId, ShiftPercentage); props.setProperty(KoSvgTextProperties::BaselineShiftValueId, 0.5); props.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(10))); props.setProperty(KoSvgTextProperties::GlyphOrientationVerticalId, fromAutoValue(AutoValue(90))); props.setProperty(KoSvgTextProperties::GlyphOrientationHorizontalId, fromAutoValue(AutoValue(180))); props.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(20))); props.setProperty(KoSvgTextProperties::WordSpacingId, fromAutoValue(AutoValue(30))); KoSvgTextProperties newProps = props; newProps.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(11))); newProps.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(21))); KoSvgTextProperties diff = newProps.ownProperties(props); QVERIFY(diff.hasProperty(KoSvgTextProperties::KerningId)); QVERIFY(diff.hasProperty(KoSvgTextProperties::LetterSpacingId)); QVERIFY(!diff.hasProperty(KoSvgTextProperties::WritingModeId)); QVERIFY(!diff.hasProperty(KoSvgTextProperties::DirectionId)); } void TestSvgText::testParseFontStyles() { const QString data = "" " Hello, out there" ""; KoXmlDocument doc; QVERIFY(doc.setContent(data.toLatin1())); KoXmlElement root = doc.documentElement(); KoDocumentResourceManager resourceManager; SvgLoadingContext context(&resourceManager); context.pushGraphicsContext(); SvgStyles styles = context.styleParser().collectStyles(root); context.styleParser().parseFont(styles); //QCOMPARE(styles.size(), 3); // TODO: multiple fonts! QCOMPARE(context.currentGC()->font.family(), QString("Verdana")); { QStringList expectedFonts = {"Verdana", "Times New Roman", "serif"}; QCOMPARE(context.currentGC()->fontFamiliesList, expectedFonts); } QCOMPARE(context.currentGC()->font.pointSizeF(), 15.0); QCOMPARE(context.currentGC()->font.style(), QFont::StyleOblique); QCOMPARE(context.currentGC()->font.capitalization(), QFont::SmallCaps); QCOMPARE(context.currentGC()->font.weight(), 66); { SvgStyles fontModifier; fontModifier["font-weight"] = "bolder"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.weight(), 75); } { SvgStyles fontModifier; fontModifier["font-weight"] = "lighter"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.weight(), 66); } QCOMPARE(context.currentGC()->font.stretch(), int(QFont::ExtraCondensed)); { SvgStyles fontModifier; fontModifier["font-stretch"] = "narrower"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.stretch(), int(QFont::UltraCondensed)); } { SvgStyles fontModifier; fontModifier["font-stretch"] = "wider"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.stretch(), int(QFont::ExtraCondensed)); } { SvgStyles fontModifier; fontModifier["text-decoration"] = "underline"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.underline(), true); } { SvgStyles fontModifier; fontModifier["text-decoration"] = "overline"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.overline(), true); } { SvgStyles fontModifier; fontModifier["text-decoration"] = "line-through"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.strikeOut(), true); } { SvgStyles fontModifier; fontModifier["text-decoration"] = " line-through overline"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.underline(), false); QCOMPARE(context.currentGC()->font.strikeOut(), true); QCOMPARE(context.currentGC()->font.overline(), true); } } void TestSvgText::testParseTextStyles() { const QString data = "" " Hello, out there" ""; KoXmlDocument doc; QVERIFY(doc.setContent(data.toLatin1())); KoXmlElement root = doc.documentElement(); KoDocumentResourceManager resourceManager; SvgLoadingContext context(&resourceManager); context.pushGraphicsContext(); SvgStyles styles = context.styleParser().collectStyles(root); context.styleParser().parseFont(styles); QCOMPARE(context.currentGC()->font.family(), QString("Verdana")); KoSvgTextProperties &props = context.currentGC()->textProperties; QCOMPARE(props.property(KoSvgTextProperties::WritingModeId).toInt(), int(KoSvgText::TopToBottom)); QCOMPARE(props.property(KoSvgTextProperties::GlyphOrientationVerticalId).value(), KoSvgText::AutoValue(M_PI_2)); } #include #include #include void TestSvgText::testSimpleText() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " Hello, out there!" " " "" ""; SvgRenderTester t (data); t.test_standard("text_simple", QSize(175, 40), 72.0); KoShape *shape = t.findShape("testRect"); KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); QVERIFY(chunkShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(shape)); QCOMPARE(chunkShape->shapeCount(), 0); QCOMPARE(chunkShape->layoutInterface()->isTextNode(), true); QCOMPARE(chunkShape->layoutInterface()->numChars(), 17); QCOMPARE(chunkShape->layoutInterface()->nodeText(), QString("Hello, out there!")); QVector transform = chunkShape->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 1); QVERIFY(bool(transform[0].xPos)); QVERIFY(bool(transform[0].yPos)); QVERIFY(!transform[0].dxPos); QVERIFY(!transform[0].dyPos); QVERIFY(!transform[0].rotate); QCOMPARE(*transform[0].xPos, 7.0); QCOMPARE(*transform[0].yPos, 27.0); QVector subChunks = chunkShape->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 1); QCOMPARE(subChunks[0].text.size(), 17); //qDebug() << ppVar(subChunks[0].text); //qDebug() << ppVar(subChunks[0].transformation); //qDebug() << ppVar(subChunks[0].format); } inline KoSvgTextChunkShape* toChunkShape(KoShape *shape) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_ASSERT(chunkShape); return chunkShape; } void TestSvgText::testComplexText() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " Hello, ou" "t there cool cdata --> nice work" " " "" ""; SvgRenderTester t (data); t.test_standard("text_complex", QSize(385, 56), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); QCOMPARE(baseShape->shapeCount(), 4); QCOMPARE(baseShape->layoutInterface()->isTextNode(), false); QCOMPARE(baseShape->layoutInterface()->numChars(), 41); { // chunk 0: "Hello, " KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[0]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 7); QCOMPARE(chunk->layoutInterface()->nodeText(), QString("Hello, ")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 7); QVERIFY(bool(transform[0].xPos)); QVERIFY(!bool(transform[1].xPos)); for (int i = 0; i < 7; i++) { QVERIFY(!i || bool(transform[i].dxPos)); if (i) { QCOMPARE(*transform[i].dxPos, qreal(i)); } } QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 7); QCOMPARE(subChunks[0].text.size(), 1); QCOMPARE(*subChunks[0].transformation.xPos, 7.0); QVERIFY(!subChunks[1].transformation.xPos); } { // chunk 1: "out" KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[1]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 3); QCOMPARE(chunk->layoutInterface()->nodeText(), QString("out")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 2); QVERIFY(bool(transform[0].xPos)); QVERIFY(!bool(transform[1].xPos)); for (int i = 0; i < 2; i++) { QVERIFY(bool(transform[i].dxPos)); QCOMPARE(*transform[i].dxPos, qreal(i + 7)); } QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 2); QCOMPARE(subChunks[0].text.size(), 1); QCOMPARE(subChunks[1].text.size(), 2); } { // chunk 2: " there " KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[2]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 7); QCOMPARE(chunk->layoutInterface()->nodeText(), QString(" there ")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 0); QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 1); QCOMPARE(subChunks[0].text.size(), 7); } { // chunk 3: "cool cdata --> nice work" KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[3]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 24); QCOMPARE(chunk->layoutInterface()->nodeText(), QString("cool cdata --> nice work")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 0); QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 1); QCOMPARE(subChunks[0].text.size(), 24); } } void TestSvgText::testHindiText() { const QString data = "" "" " " " " + " font-family=\"FreeSans\" font-size=\"15\" fill=\"blue\" >" "मौखिक रूप से हिंदी के काफी सामान" " " "" ""; SvgRenderTester t (data); + + QFont testFont("FreeSans"); + if (!QFontInfo(testFont).exactMatch()) { + QEXPECT_FAIL(0, "FreeSans found is *not* found! Hindi rendering might be broken!", Continue); + } + t.test_standard("text_hindi", QSize(260, 30), 72); } void TestSvgText::testTextBaselineShift() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " textsuper normalsub" " " "" ""; SvgRenderTester t (data); t.test_standard("text_baseline_shift", QSize(180, 40), 72); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } void TestSvgText::testTextSpacing() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " Lorem ipsum" " Lorem ipsum (ls=4)" " Lorem ipsum (ls=-2)" " Lorem ipsum" " Lorem ipsum (ws=4)" " Lorem ipsum (ws=-2)" " Lorem ipsum" " Lorem ipsum (k=0)" " Lorem ipsum (k=2)" " Lorem ipsum (k=2,ls=2)" " " "" ""; SvgRenderTester t (data); + t.setFuzzyThreshold(5); t.test_standard("text_letter_word_spacing", QSize(340, 250), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } void TestSvgText::testTextDecorations() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " Lorem ipsum" " Lorem ipsum" " Lorem ipsum" " Lorem ipsum (WRONG!!!)" " " "" ""; SvgRenderTester t (data); + t.setFuzzyThreshold(5); t.test_standard("text_decorations", QSize(290, 135), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } void TestSvgText::testRightToLeft() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" text-anchor=\"end\">" " aa bb cc dd" " حادثتا السفينتين «بسين Bassein» و«فايبر Viper»" " *" " aa bb حادثتا السفينتين بسين cc dd " " aa bb حادثتا السفينتين بسين cc dd " " *" " aa bb حادثتا السفينتين بسين cc dd " " aa bb حادثتا السفينتين بسين cc dd " " aa bb حادثتا السفينتين بسين cc dd " " *" " الناطقون: 295 مليون - 422 مليون" " Spoken: 295 مليون - 422 مليون " " *" " aa bb c1 c2 c3 c4 dd ee" " " "" ""; SvgRenderTester t (data); t.test_standard("text_right_to_left", QSize(500,450), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } #include #include void TestSvgText::testQtBidi() { // Arabic text sample from Wikipedia: // https://ar.wikipedia.org/wiki/%D8%A5%D9%85%D8%A7%D8%B1%D8%A7%D8%AA_%D8%A7%D9%84%D8%B3%D8%A7%D8%AD%D9%84_%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D8%A7%D9%84%D8%AD QStringList ltrText; ltrText << "aa bb cc dd"; ltrText << "aa bb حادثتا السفينتين بسين cc dd"; ltrText << "aa bb \u202ec1c2 d3d4\u202C ee ff"; QStringList rtlText; rtlText << "حادثتا السفينتين «بسين Bassein» و«فايبر Viper»"; rtlText << "حادثتا السفينتين «بسين aa bb cc dd» و«فايبر Viper»"; QImage canvas(500,500,QImage::Format_ARGB32); QPainter gc(&canvas); QPointF pos(15,15); QVector textSamples; textSamples << ltrText; textSamples << rtlText; QVector textDirections; textDirections << Qt::LeftToRight; textDirections << Qt::RightToLeft; for (int i = 0; i < textSamples.size(); i++) { Q_FOREACH (const QString str, textSamples[i]) { QTextOption option; option.setTextDirection(textDirections[i]); option.setUseDesignMetrics(true); QTextLayout layout; layout.setText(str); layout.setFont(QFont("serif", 15.0)); layout.setCacheEnabled(true); layout.beginLayout(); QTextLine line = layout.createLine(); line.setPosition(pos); pos.ry() += 25; layout.endLayout(); layout.draw(&gc, QPointF()); } } canvas.save("test_bidi.png"); } void TestSvgText::testQtDxDy() { QImage canvas(500,500,QImage::Format_ARGB32); QPainter gc(&canvas); QPointF pos(15,15); QTextOption option; option.setTextDirection(Qt::LeftToRight); option.setUseDesignMetrics(true); option.setWrapMode(QTextOption::WrapAnywhere); QTextLayout layout; layout.setText("aa bb cc dd ee ff"); layout.setFont(QFont("serif", 15.0)); layout.setCacheEnabled(true); layout.beginLayout(); layout.setTextOption(option); { QTextLine line = layout.createLine(); line.setPosition(pos); line.setNumColumns(4); } pos.ry() += 25; pos.rx() += 30; { QTextLine line = layout.createLine(); line.setPosition(pos); } layout.endLayout(); layout.draw(&gc, QPointF()); canvas.save("test_dxdy.png"); } void TestSvgText::testTextOutlineSolid() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" stroke=\"red\" stroke-width=\"1\">" " SA" " " "" ""; SvgRenderTester t (data); t.test_standard("text_outline_solid", QSize(30, 30), 72.0); } void TestSvgText::testNbspHandling() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" stroke=\"red\" stroke-width=\"1\">" " S\u00A0A" " " "" ""; SvgRenderTester t (data); t.test_standard("text_nbsp", QSize(30, 30), 72.0); } void TestSvgText::testMulticolorText() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " SA" " " "" ""; SvgRenderTester t (data); + t.setFuzzyThreshold(5); t.test_standard("text_multicolor", QSize(30, 30), 72.0); } #include #include void TestSvgText::testConvertToStrippedSvg() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " SAsome stuff<><><<<>" " " "" ""; SvgRenderTester t (data); t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); t.run(); KoSvgTextShape *baseShape = dynamic_cast(t.findShape("testRect")); QVERIFY(baseShape); { KoColorBackground *bg = dynamic_cast(baseShape->background().data()); QVERIFY(bg); QCOMPARE(bg->color(), QColor(Qt::blue)); } KoSvgTextShapeMarkupConverter converter(baseShape); QString svgText; QString stylesText; QVERIFY(converter.convertToSvg(&svgText, &stylesText)); QCOMPARE(stylesText, QString("")); - QCOMPARE(svgText, QString("SAsome stuff<><><<<>")); + QCOMPARE(svgText, QString("SAsome stuff<><><<<>")); // test loading - svgText = "SAsome stuff<><><<<>"; + svgText = "SAsome stuff<><><<<>"; QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); { KoColorBackground *bg = dynamic_cast(baseShape->background().data()); QVERIFY(bg); QCOMPARE(bg->color(), QColor(Qt::green)); } { KoSvgTextProperties props = baseShape->textProperties(); QVERIFY(props.hasProperty(KoSvgTextProperties::FontSizeId)); const qreal fontSize = props.property(KoSvgTextProperties::FontSizeId).toReal(); QCOMPARE(fontSize, 19.0); } QCOMPARE(baseShape->shapeCount(), 3); } void TestSvgText::testConvertToStrippedSvgNullOrigin() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " SAsome stuff<><><<<>" " " "" ""; SvgRenderTester t (data); t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); t.run(); KoSvgTextShape *baseShape = dynamic_cast(t.findShape("testRect")); QVERIFY(baseShape); KoSvgTextShapeMarkupConverter converter(baseShape); QString svgText; QString stylesText; QVERIFY(converter.convertToSvg(&svgText, &stylesText)); QCOMPARE(stylesText, QString("")); - QCOMPARE(svgText, QString("SAsome stuff<><><<<>")); + QCOMPARE(svgText, QString("SAsome stuff<><><<<>")); } void TestSvgText::testConvertFromIncorrectStrippedSvg() { QScopedPointer baseShape(new KoSvgTextShape()); KoSvgTextShapeMarkupConverter converter(baseShape.data()); QString svgText; QString stylesText; svgText = "blah text"; QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); QCOMPARE(converter.errors().size(), 0); svgText = ">><<>"; QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); qDebug() << ppVar(converter.errors()); QCOMPARE(converter.errors().size(), 1); svgText = "blah text"; QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); qDebug() << ppVar(converter.errors()); QCOMPARE(converter.errors().size(), 1); svgText = ""; QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); qDebug() << ppVar(converter.errors()); QCOMPARE(converter.errors().size(), 1); } void TestSvgText::testEmptyTextChunk() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " " // no actual text! should not crash! " " "" ""; SvgRenderTester t (data); // it just shouldn't assert or fail when seeing an empty text block t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); t.run(); } void TestSvgText::testConvertHtmlToSvg() { const QString html = "" "" "" "" "" "" "" "" "

" " Lorem ipsum dolor" "

" "

sit am" "et, consectetur adipiscing

" "

" " elit. " "

" "" ""; KoSvgTextShape shape; KoSvgTextShapeMarkupConverter converter(&shape); QString svg; QString defs; converter.convertFromHtml(html, &svg, &defs); bool r = converter.convertToSvg(&svg, &defs); qDebug() << r << svg << defs; } void TestSvgText::testTextWithMultipleRelativeOffsets() { const QString data = "" "" " " " Lorem ipsum dolor sit amet" " " "" ""; SvgRenderTester t (data); + t.setFuzzyThreshold(5); t.test_standard("text_multiple_relative_offsets", QSize(300, 80), 72.0); } void TestSvgText::testTextWithMultipleAbsoluteOffsetsArabic() { /** * According to the standard, each **absolute** offset defines a * new text chunk, therefore, the arabic text must become * ltr reordered */ const QString data = "" "" " " " Lo rem اللغة العربية المعيارية الحديثة ip sum" " " "" ""; SvgRenderTester t (data); t.test_standard("text_multiple_absolute_offsets_arabic", QSize(530, 70), 72.0); } void TestSvgText::testTextWithMultipleRelativeOffsetsArabic() { /** * According to the standard, **relative** offsets must not define a new * text chunk, therefore, the arabic text must be written in native rtl order, * even though the individual letters are split. */ const QString data = "" "" " " " Lo rem اللغة العربية المعيارية الحديثة ip sum" " " "" ""; SvgRenderTester t (data); - + // we cannot expect more than one failure +#ifndef USE_ROUND_TRIP QEXPECT_FAIL("", "WARNING: in Krita relative offsets also define a new text chunk, that doesn't comply with SVG standard and must be fixed", Continue); t.test_standard("text_multiple_relative_offsets_arabic", QSize(530, 70), 72.0); +#endif } void TestSvgText::testTextOutline() { const QString data = "" "" " " " " + " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" " normal " " strikethrough" " overline" " underline" " " "" ""; QRect renderRect(0, 0, 450, 40); SvgRenderTester t (data); + t.setFuzzyThreshold(5); t.test_standard("text_outline", renderRect.size(), 72.0); KoShape *shape = t.findShape("testRect"); KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); QVERIFY(chunkShape); KoSvgTextShape *textShape = dynamic_cast(shape); QImage canvas(renderRect.size(), QImage::Format_ARGB32); canvas.fill(0); QPainter gc(&canvas); gc.setPen(Qt::NoPen); gc.setBrush(Qt::black); gc.setRenderHint(QPainter::Antialiasing, true); gc.drawPath(textShape->textOutline()); QVERIFY(TestUtil::checkQImage(canvas, "svg_render", "load_text_outline", "converted_to_path", 3, 5)); } QTEST_MAIN(TestSvgText) diff --git a/libs/flake/tests/data/svg_render/load_image_aspect_default.png b/libs/flake/tests/data/svg_render/load_image_aspect_default.png index c5749a5098..34666ca23f 100644 Binary files a/libs/flake/tests/data/svg_render/load_image_aspect_default.png and b/libs/flake/tests/data/svg_render/load_image_aspect_default.png differ diff --git a/libs/flake/tests/data/svg_render/load_text_baseline_shift.png b/libs/flake/tests/data/svg_render/load_text_baseline_shift.png index 59ac7bf9d0..244995591d 100644 Binary files a/libs/flake/tests/data/svg_render/load_text_baseline_shift.png and b/libs/flake/tests/data/svg_render/load_text_baseline_shift.png differ diff --git a/libs/flake/tests/data/svg_render/load_text_decorations.png b/libs/flake/tests/data/svg_render/load_text_decorations.png index 1377f5f03f..437280ca1e 100644 Binary files a/libs/flake/tests/data/svg_render/load_text_decorations.png and b/libs/flake/tests/data/svg_render/load_text_decorations.png differ diff --git a/libs/flake/tests/data/svg_render/load_text_hindi.png b/libs/flake/tests/data/svg_render/load_text_hindi.png index e6fd1f9366..397950833d 100644 Binary files a/libs/flake/tests/data/svg_render/load_text_hindi.png and b/libs/flake/tests/data/svg_render/load_text_hindi.png differ diff --git a/libs/flake/tests/data/svg_render/load_text_multiple_absolute_offsets_arabic.png b/libs/flake/tests/data/svg_render/load_text_multiple_absolute_offsets_arabic.png index 0120741001..9afb6f8583 100644 Binary files a/libs/flake/tests/data/svg_render/load_text_multiple_absolute_offsets_arabic.png and b/libs/flake/tests/data/svg_render/load_text_multiple_absolute_offsets_arabic.png differ diff --git a/libs/flake/tests/data/svg_render/load_text_right_to_left.png b/libs/flake/tests/data/svg_render/load_text_right_to_left.png index fedd01354f..b9f8e081e3 100644 Binary files a/libs/flake/tests/data/svg_render/load_text_right_to_left.png and b/libs/flake/tests/data/svg_render/load_text_right_to_left.png differ diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index c38bd98be3..b515ef9b0b 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,388 +1,376 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/metadata ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_set_projection_color_space_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/kis_set_global_selection_command.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc kis_node_uuid_info.cpp kis_clone_layer.cpp kis_colorspace_convert_visitor.cpp kis_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp - KisUpdateSchedulerConfigNotifier.cpp + KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h metadata/kis_meta_data_entry.cc metadata/kis_meta_data_filter.cc metadata/kis_meta_data_filter_p.cc metadata/kis_meta_data_filter_registry.cc metadata/kis_meta_data_filter_registry_model.cc metadata/kis_meta_data_io_backend.cc metadata/kis_meta_data_merge_strategy.cc metadata/kis_meta_data_merge_strategy_p.cc metadata/kis_meta_data_merge_strategy_registry.cc metadata/kis_meta_data_parser.cc metadata/kis_meta_data_schema.cc metadata/kis_meta_data_schema_registry.cc metadata/kis_meta_data_store.cc metadata/kis_meta_data_type_info.cc metadata/kis_meta_data_validator.cc metadata/kis_meta_data_value.cc kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_psd_layer_style.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp KisProofingConfiguration.cpp kis_node_query_path.cc ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets kritaglobal kritapsd kritaodf kritapigment kritacommand kritawidgetutils Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) - -########### install schemas ############# -install( FILES - metadata/schemas/dc.schema - metadata/schemas/exif.schema - metadata/schemas/tiff.schema - metadata/schemas/mkn.schema - metadata/schemas/xmp.schema - metadata/schemas/xmpmm.schema - metadata/schemas/xmprights.schema - - DESTINATION ${DATA_INSTALL_DIR}/krita/metadata/schemas) diff --git a/libs/image/KisUpdateSchedulerConfigNotifier.cpp b/libs/image/KisImageConfigNotifier.cpp similarity index 73% rename from libs/image/KisUpdateSchedulerConfigNotifier.cpp rename to libs/image/KisImageConfigNotifier.cpp index 0fa9f10aca..829722afa7 100644 --- a/libs/image/KisUpdateSchedulerConfigNotifier.cpp +++ b/libs/image/KisImageConfigNotifier.cpp @@ -1,53 +1,53 @@ /* * 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 "KisUpdateSchedulerConfigNotifier.h" +#include "KisImageConfigNotifier.h" #include #include #include "kis_signal_compressor.h" -Q_GLOBAL_STATIC(KisUpdateSchedulerConfigNotifier, s_instance) +Q_GLOBAL_STATIC(KisImageConfigNotifier, s_instance) -struct KisUpdateSchedulerConfigNotifier::Private +struct KisImageConfigNotifier::Private { Private() : updateCompressor(300, KisSignalCompressor::FIRST_ACTIVE) {} KisSignalCompressor updateCompressor; }; -KisUpdateSchedulerConfigNotifier::KisUpdateSchedulerConfigNotifier() +KisImageConfigNotifier::KisImageConfigNotifier() : m_d(new Private) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SIGNAL(configChanged())); } -KisUpdateSchedulerConfigNotifier::~KisUpdateSchedulerConfigNotifier() +KisImageConfigNotifier::~KisImageConfigNotifier() { } -KisUpdateSchedulerConfigNotifier *KisUpdateSchedulerConfigNotifier::instance() +KisImageConfigNotifier *KisImageConfigNotifier::instance() { return s_instance; } -void KisUpdateSchedulerConfigNotifier::notifyConfigChanged() +void KisImageConfigNotifier::notifyConfigChanged() { m_d->updateCompressor.start(); } diff --git a/libs/image/KisUpdateSchedulerConfigNotifier.h b/libs/image/KisImageConfigNotifier.h similarity index 75% rename from libs/image/KisUpdateSchedulerConfigNotifier.h rename to libs/image/KisImageConfigNotifier.h index e1815513c9..af3cb02bc9 100644 --- a/libs/image/KisUpdateSchedulerConfigNotifier.h +++ b/libs/image/KisImageConfigNotifier.h @@ -1,54 +1,54 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KISUPDATESCHEDULERCONFIGNOTIFIER_H -#define KISUPDATESCHEDULERCONFIGNOTIFIER_H +#ifndef KISIMAGECONFIGNOTIFIER_H +#define KISIMAGECONFIGNOTIFIER_H #include #include "kritaimage_export.h" -class KRITAIMAGE_EXPORT KisUpdateSchedulerConfigNotifier : public QObject +class KRITAIMAGE_EXPORT KisImageConfigNotifier : public QObject { Q_OBJECT public: - explicit KisUpdateSchedulerConfigNotifier(); - ~KisUpdateSchedulerConfigNotifier() override; + explicit KisImageConfigNotifier(); + ~KisImageConfigNotifier() override; - static KisUpdateSchedulerConfigNotifier* instance(); + static KisImageConfigNotifier* instance(); /** * Notify that the configuration has changed. This will cause the * configChanged() signal to be emitted. */ void notifyConfigChanged(void); Q_SIGNALS: /** * This signal is emitted whenever notifyConfigChanged() is called. */ void configChanged(void); private: - Q_DISABLE_COPY(KisUpdateSchedulerConfigNotifier) + Q_DISABLE_COPY(KisImageConfigNotifier) private: struct Private; const QScopedPointer m_d; }; -#endif // KISUPDATESCHEDULERCONFIGNOTIFIER_H +#endif // KISIMAGECONFIGNOTIFIER_H diff --git a/libs/image/commands/kis_node_property_list_command.cpp b/libs/image/commands/kis_node_property_list_command.cpp index b2ba365f5f..d438bda807 100644 --- a/libs/image/commands/kis_node_property_list_command.cpp +++ b/libs/image/commands/kis_node_property_list_command.cpp @@ -1,219 +1,226 @@ /* * Copyright (c) 2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_node.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection_mask.h" #include "kis_paint_layer.h" #include "commands/kis_node_property_list_command.h" #include "kis_undo_adapter.h" #include "kis_layer_properties_icons.h" // HACK! please refactor out! #include "kis_simple_stroke_strategy.h" KisNodePropertyListCommand::KisNodePropertyListCommand(KisNodeSP node, KisBaseNode::PropertyList newPropertyList) : KisNodeCommand(kundo2_i18n("Property Changes"), node), m_newPropertyList(newPropertyList), m_oldPropertyList(node->sectionModelProperties()) /** * TODO instead of "Property Changes" check which property * has been changed and display either lock/unlock, visible/hidden * or "Property Changes" (this require new strings) */ { } void KisNodePropertyListCommand::redo() { const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); const QRect oldExtent = m_node->extent(); m_node->setSectionModelProperties(m_newPropertyList); doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->extent()); } void KisNodePropertyListCommand::undo() { const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); const QRect oldExtent = m_node->extent(); m_node->setSectionModelProperties(m_oldPropertyList); doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->extent()); } bool checkOnionSkinChanged(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList) { if (oldPropertyList.size() != newPropertyList.size()) return false; bool oldOnionSkinsValue = false; bool newOnionSkinsValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { oldOnionSkinsValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { newOnionSkinsValue = prop.state.toBool(); } } return oldOnionSkinsValue != newOnionSkinsValue; } void KisNodePropertyListCommand::doUpdate(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList, const QRect &totalUpdateExtent) { /** * Sometimes the node might refuse to change the property, e.g. needs-update for colorize * mask. In this case we should avoid issuing the update and set-modified call. */ if (oldPropertyList == newPropertyList) { return; } bool oldPassThroughValue = false; bool newPassThroughValue = false; bool oldVisibilityValue = false; bool newVisibilityValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { oldPassThroughValue = prop.state.toBool(); } if (prop.id == KisLayerPropertiesIcons::visible.id()) { oldVisibilityValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { newPassThroughValue = prop.state.toBool(); } if (prop.id == KisLayerPropertiesIcons::visible.id()) { newVisibilityValue = prop.state.toBool(); } } if (oldPassThroughValue && !newPassThroughValue) { KisLayerSP layer(qobject_cast(m_node.data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if ((m_node->parent() && !oldPassThroughValue && newPassThroughValue) || (oldPassThroughValue && newPassThroughValue && !oldVisibilityValue && newVisibilityValue)) { KisLayerSP layer(qobject_cast(m_node->parent().data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if (checkOnionSkinChanged(oldPropertyList, newPropertyList)) { m_node->setDirtyDontResetAnimationCache(totalUpdateExtent); } else { m_node->setDirty(totalUpdateExtent); // TODO check if visibility was actually changed or not } } void KisNodePropertyListCommand::setNodePropertiesNoUndo(KisNodeSP node, KisImageSP image, PropertyList proplist) { QVector undo; Q_FOREACH (const KisBaseNode::Property &prop, proplist) { if (prop.isInStasis) undo << false; if (prop.name == i18n("Visible") && node->visible() != prop.state.toBool()) { undo << false; continue; } else if (prop.name == i18n("Locked") && node->userLocked() != prop.state.toBool()) { undo << false; continue; } else if (prop.name == i18n("Active")) { if (KisSelectionMask *m = dynamic_cast(node.data())) { if (m->active() != prop.state.toBool()) { undo << false; continue; } } } else if (prop.name == i18n("Alpha Locked")) { if (KisPaintLayer* l = dynamic_cast(node.data())) { if (l->alphaLocked() != prop.state.toBool()) { undo << false; continue; } } } // This property is known, but it hasn't got the same value, and it isn't one of // the previous properties, so we need to add the command to the undo list. Q_FOREACH(const KisBaseNode::Property &p2, node->sectionModelProperties()) { if (p2.name == prop.name && p2.state != prop.state) { undo << true; break; } } } QScopedPointer cmd(new KisNodePropertyListCommand(node, proplist)); if (undo.contains(true)) { image->undoAdapter()->addCommand(cmd.take()); image->setModified(); } else { - cmd->redo(); - /** * HACK ALERT! * * Here we start a fake legacy stroke, so that all the LoD planes would * be invalidated. Ideally, we should refactor this method and avoid * resetting LoD planes when node visibility changes, Instead there should * be two commands executes: LoD agnostic one (which sets the properties * themselves), and two LoD-specific update commands: one for lodN and * another one for lod0. */ struct SimpleLodResettingStroke : public KisSimpleStrokeStrategy { - SimpleLodResettingStroke() + SimpleLodResettingStroke(KUndo2Command *cmd) + : m_cmd(cmd) { setClearsRedoOnStart(false); + this->enableJob(JOB_INIT, true); + } + + void initStrokeCallback() { + m_cmd->redo(); } + + private: + QScopedPointer m_cmd; }; - KisStrokeId strokeId = image->startStroke(new SimpleLodResettingStroke()); + KisStrokeId strokeId = image->startStroke(new SimpleLodResettingStroke(cmd.take())); image->endStroke(strokeId); } } diff --git a/libs/image/kis_async_merger.cpp b/libs/image/kis_async_merger.cpp index 6e8806e12e..d0a2dd93f7 100644 --- a/libs/image/kis_async_merger.cpp +++ b/libs/image/kis_async_merger.cpp @@ -1,365 +1,368 @@ /* Copyright (c) Dmitry Kazakov , 2009 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_async_merger.h" #include #include #include #include #include "kis_node_visitor.h" #include "kis_painter.h" #include "kis_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "generator/kis_generator_layer.h" #include "kis_external_layer_iface.h" #include "kis_paint_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_clone_layer.h" #include "kis_processing_information.h" #include "kis_busy_progress_indicator.h" #include "kis_merge_walker.h" #include "kis_refresh_subtree_walker.h" #include "kis_abstract_projection_plane.h" //#define DEBUG_MERGER #ifdef DEBUG_MERGER #define DEBUG_NODE_ACTION(message, type, leaf, rect) \ dbgImage << message << type << ":" << leaf->node()->name() << rect #else #define DEBUG_NODE_ACTION(message, type, leaf, rect) #endif class KisUpdateOriginalVisitor : public KisNodeVisitor { public: KisUpdateOriginalVisitor(const QRect &updateRect, KisPaintDeviceSP projection, const QRect &cropRect) : m_updateRect(updateRect), m_cropRect(cropRect), m_projection(projection) { } ~KisUpdateOriginalVisitor() override { } public: using KisNodeVisitor::visit; bool visit(KisAdjustmentLayer* layer) override { if (!layer->visible()) return true; if (!m_projection) { warnImage << "ObligeChild mechanism has been activated for " "an adjustment layer! Do nothing..."; layer->original()->clear(); return true; } KisPaintDeviceSP originalDevice = layer->original(); originalDevice->clear(m_updateRect); const QRect applyRect = m_updateRect & m_projection->extent(); // If the intersection of the updaterect and the projection extent is // null, we are finish here. if(applyRect.isNull()) return true; KisFilterConfigurationSP filterConfig = layer->filter(); if (!filterConfig) { /** * When an adjustment layer is just created, it may have no * filter inside. Then the layer has work as a pass-through * node. Just copy the merged data to the layer's original. */ KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect); return true; } KisSelectionSP selection = layer->fetchComposedInternalSelection(applyRect); const QRect filterRect = selection ? applyRect & selection->selectedRect() : applyRect; KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name()); if (!filter) return false; KisPaintDeviceSP dstDevice = originalDevice; if (selection) { dstDevice = new KisPaintDevice(originalDevice->colorSpace()); } if (!filterRect.isEmpty()) { KIS_ASSERT_RECOVER_NOOP(layer->busyProgressIndicator()); layer->busyProgressIndicator()->update(); // We do not create a transaction here, as srcDevice != dstDevice filter->process(m_projection, dstDevice, 0, filterRect, filterConfig.data(), 0); } if (selection) { KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect); KisPainter::copyAreaOptimized(filterRect.topLeft(), dstDevice, originalDevice, filterRect, selection); } return true; } bool visit(KisExternalLayer*) override { return true; } bool visit(KisGeneratorLayer*) override { return true; } bool visit(KisPaintLayer*) override { return true; } bool visit(KisGroupLayer*) override { return true; } bool visit(KisCloneLayer *layer) override { QRect emptyRect; KisRefreshSubtreeWalker walker(emptyRect); KisAsyncMerger merger; KisLayerSP srcLayer = layer->copyFrom(); QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y()); QRegion prepareRegion(srcRect); prepareRegion -= m_cropRect; /** * If a clone has complicated masks, we should prepare additional * source area to ensure the rect is prepared. */ QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect); if (!needRectOnSource.isEmpty()) { prepareRegion += needRectOnSource; } Q_FOREACH (const QRect &rect, prepareRegion.rects()) { walker.collectRects(srcLayer, rect); merger.startMerge(walker, false); } return true; } bool visit(KisNode*) override { return true; } bool visit(KisFilterMask*) override { return true; } bool visit(KisTransformMask*) override { return true; } bool visit(KisTransparencyMask*) override { return true; } bool visit(KisSelectionMask*) override { return true; } bool visit(KisColorizeMask*) override { return true; } private: QRect m_updateRect; QRect m_cropRect; KisPaintDeviceSP m_projection; }; /*********************************************************************/ /* KisAsyncMerger */ /*********************************************************************/ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) { KisMergeWalker::LeafStack &leafStack = walker.leafStack(); const bool useTempProjections = walker.needRectVaries(); while(!leafStack.isEmpty()) { KisMergeWalker::JobItem item = leafStack.pop(); KisProjectionLeafSP currentLeaf = item.m_leaf; - if(currentLeaf->isRoot()) continue; - // All the masks should be filtered by the walkers - Q_ASSERT(currentLeaf->isLayer()); + KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->isLayer()); QRect applyRect = item.m_applyRect; + if(currentLeaf->isRoot()) { + currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); + continue; + } + if(item.m_position & KisMergeWalker::N_EXTRA) { // The type of layers that will not go to projection. DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentLeaf, applyRect); KisUpdateOriginalVisitor originalVisitor(applyRect, m_currentProjection, walker.cropRect()); currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node()); continue; } if(!m_currentProjection) setupProjection(currentLeaf, applyRect, useTempProjections); KisUpdateOriginalVisitor originalVisitor(applyRect, m_currentProjection, walker.cropRect()); if(item.m_position & KisMergeWalker::N_FILTHY) { DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentLeaf, applyRect); if (currentLeaf->visible()) { currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); } } else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) { DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentLeaf, applyRect); if(currentLeaf->dependsOnLowerNodes()) { if (currentLeaf->visible()) { currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node()); } } } else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) { DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentLeaf, applyRect); if (currentLeaf->visible()) { currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); } } else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ { DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentLeaf, applyRect); /* nothing to do */ } compositeWithProjection(currentLeaf, applyRect); if(item.m_position & KisMergeWalker::N_TOPMOST) { writeProjection(currentLeaf, useTempProjections, applyRect); resetProjection(); } // FIXME: remove it from the inner loop and/or change to a warning! Q_ASSERT(currentLeaf->projection()->defaultBounds()->currentLevelOfDetail() == walker.levelOfDetail()); } if(notifyClones) { doNotifyClones(walker); } if(m_currentProjection) { warnImage << "BUG: The walker hasn't reached the root layer!"; warnImage << " Start node:" << walker.startNode() << "Requested rect:" << walker.requestedRect(); warnImage << " An inconsistency in the walkers occurred!"; warnImage << " Please report a bug describing how you got this message."; // reset projection to avoid artifacts in next merges and allow people to work further resetProjection(); } } void KisAsyncMerger::resetProjection() { m_currentProjection = 0; m_finalProjection = 0; } void KisAsyncMerger::setupProjection(KisProjectionLeafSP currentLeaf, const QRect& rect, bool useTempProjection) { KisPaintDeviceSP parentOriginal = currentLeaf->parent()->original(); if (parentOriginal != currentLeaf->projection()) { if (useTempProjection) { if(!m_cachedPaintDevice) m_cachedPaintDevice = new KisPaintDevice(parentOriginal->colorSpace()); m_currentProjection = m_cachedPaintDevice; m_currentProjection->prepareClone(parentOriginal); m_finalProjection = parentOriginal; } else { parentOriginal->clear(rect); m_finalProjection = m_currentProjection = parentOriginal; } } else { /** * It happened so that our parent uses our own projection as * its original. It means obligeChild mechanism works. * We won't initialise m_currentProjection. This will cause * writeProjection() and compositeWithProjection() do nothing * when called. */ /* NOP */ } } void KisAsyncMerger::writeProjection(KisProjectionLeafSP topmostLeaf, bool useTempProjection, const QRect &rect) { Q_UNUSED(useTempProjection); Q_UNUSED(topmostLeaf); if (!m_currentProjection) return; if(m_currentProjection != m_finalProjection) { KisPainter::copyAreaOptimized(rect.topLeft(), m_currentProjection, m_finalProjection, rect); } DEBUG_NODE_ACTION("Writing projection", "", topmostLeaf->parent(), rect); } bool KisAsyncMerger::compositeWithProjection(KisProjectionLeafSP leaf, const QRect &rect) { if (!m_currentProjection) return true; if (!leaf->visible()) return true; KisPainter gc(m_currentProjection); leaf->projectionPlane()->apply(&gc, rect); DEBUG_NODE_ACTION("Compositing projection", "", leaf, rect); return true; } void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) { KisBaseRectsWalker::CloneNotificationsVector &vector = walker.cloneNotifications(); KisBaseRectsWalker::CloneNotificationsVector::iterator it; for(it = vector.begin(); it != vector.end(); ++it) { (*it).notify(); } } diff --git a/libs/image/kis_base_rects_walker.h b/libs/image/kis_base_rects_walker.h index e1ab4362a1..404a548145 100644 --- a/libs/image/kis_base_rects_walker.h +++ b/libs/image/kis_base_rects_walker.h @@ -1,492 +1,492 @@ /* * 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. */ #ifndef __KIS_BASE_RECTS_WALKER_H #define __KIS_BASE_RECTS_WALKER_H #include #include "kis_layer.h" #include "kis_abstract_projection_plane.h" #include "kis_projection_leaf.h" class KisBaseRectsWalker; typedef KisSharedPtr KisBaseRectsWalkerSP; class KRITAIMAGE_EXPORT KisBaseRectsWalker : public KisShared { public: enum UpdateType { UPDATE, UPDATE_NO_FILTHY, FULL_REFRESH, UNSUPPORTED }; typedef qint32 NodePosition; enum NodePositionValues { /** * There are two different sets of values. * The first describes the position of the node to the graph, * the second shows the position to the filthy node */ N_NORMAL = 0x00, N_TOPMOST = 0x01, N_BOTTOMMOST = 0x02, N_EXTRA = 0x04, N_ABOVE_FILTHY = 0x08, N_FILTHY_ORIGINAL = 0x10, // not used actually N_FILTHY_PROJECTION = 0x20, N_FILTHY = 0x40, N_BELOW_FILTHY = 0x80 }; #define GRAPH_POSITION_MASK 0x07 static inline KisNode::PositionToFilthy convertPositionToFilthy(NodePosition position) { static const int positionToFilthyMask = N_ABOVE_FILTHY | N_FILTHY_PROJECTION | N_FILTHY | N_BELOW_FILTHY; qint32 positionToFilthy = position & N_EXTRA ? N_FILTHY : position & positionToFilthyMask; // We do not use N_FILTHY_ORIGINAL yet, so... Q_ASSERT(positionToFilthy); return static_cast(positionToFilthy); } struct CloneNotification { CloneNotification() {} CloneNotification(KisNodeSP node, const QRect &dirtyRect) : m_layer(qobject_cast(node.data())), m_dirtyRect(dirtyRect) {} void notify() { Q_ASSERT(m_layer); // clones are possible for layers only m_layer->updateClones(m_dirtyRect); } private: friend class KisWalkersTest; KisLayerSP m_layer; QRect m_dirtyRect; }; typedef QVector CloneNotificationsVector; struct JobItem { KisProjectionLeafSP m_leaf; NodePosition m_position; /** * The rect that should be prepared on this node. * E.g. area where the filter applies on filter layer * or an area of a paint layer that will be copied to * the projection. */ QRect m_applyRect; }; typedef QStack LeafStack; public: KisBaseRectsWalker() : m_levelOfDetail(0) { } virtual ~KisBaseRectsWalker() { } void collectRects(KisNodeSP node, const QRect& requestedRect) { clear(); KisProjectionLeafSP startLeaf = node->projectionLeaf(); m_nodeChecksum = calculateChecksum(startLeaf, requestedRect); m_graphChecksum = node->graphSequenceNumber(); m_resultChangeRect = requestedRect; m_resultUncroppedChangeRect = requestedRect; m_requestedRect = requestedRect; m_startNode = node; m_levelOfDetail = getNodeLevelOfDetail(startLeaf); startTrip(startLeaf); } inline void recalculate(const QRect& requestedRect) { Q_ASSERT(m_startNode); KisProjectionLeafSP startLeaf = m_startNode->projectionLeaf(); int calculatedLevelOfDetail = getNodeLevelOfDetail(startLeaf); if (m_levelOfDetail != calculatedLevelOfDetail) { qWarning() << "WARNING: KisBaseRectsWalker::recalculate()" << "The levelOfDetail has changes with time," << "which couldn't have happened!" << ppVar(m_levelOfDetail) << ppVar(calculatedLevelOfDetail); m_levelOfDetail = calculatedLevelOfDetail; } if(startLeaf->isStillInGraph()) { collectRects(m_startNode, requestedRect); } else { clear(); m_nodeChecksum = calculateChecksum(startLeaf, requestedRect); m_graphChecksum = m_startNode->graphSequenceNumber(); m_resultChangeRect = QRect(); m_resultUncroppedChangeRect = QRect(); } } bool checksumValid() { Q_ASSERT(m_startNode); return m_nodeChecksum == calculateChecksum(m_startNode->projectionLeaf(), m_requestedRect) && m_graphChecksum == m_startNode->graphSequenceNumber(); } inline void setCropRect(QRect cropRect) { m_cropRect = cropRect; } inline QRect cropRect() const{ return m_cropRect; } // return a reference for efficiency reasons inline LeafStack& leafStack() { return m_mergeTask; } // return a reference for efficiency reasons inline CloneNotificationsVector& cloneNotifications() { return m_cloneNotifications; } inline QRect accessRect() const { return m_resultAccessRect; } inline QRect changeRect() const { return m_resultChangeRect; } inline QRect uncroppedChangeRect() const { return m_resultUncroppedChangeRect; } inline bool needRectVaries() const { return m_needRectVaries; } inline bool changeRectVaries() const { return m_changeRectVaries; } inline KisNodeSP startNode() const { return m_startNode; } inline QRect requestedRect() const { return m_requestedRect; } inline int levelOfDetail() const { return m_levelOfDetail; } virtual UpdateType type() const = 0; protected: /** * Initiates collecting of rects. * Should be implemented in derived classes */ virtual void startTrip(KisProjectionLeafSP startWith) = 0; protected: static inline qint32 getGraphPosition(qint32 position) { return position & GRAPH_POSITION_MASK; } static inline bool hasClones(KisNodeSP node) { KisLayer *layer = qobject_cast(node.data()); return layer && layer->hasClones(); } static inline NodePosition calculateNodePosition(KisProjectionLeafSP leaf) { KisProjectionLeafSP nextLeaf = leaf->nextSibling(); while(nextLeaf && !nextLeaf->isLayer()) nextLeaf = nextLeaf->nextSibling(); if (!nextLeaf) return N_TOPMOST; KisProjectionLeafSP prevLeaf = leaf->prevSibling(); while(prevLeaf && !prevLeaf->isLayer()) prevLeaf = prevLeaf->prevSibling(); if (!prevLeaf) return N_BOTTOMMOST; return N_NORMAL; } inline bool isStartLeaf(KisProjectionLeafSP leaf) const { return leaf->node() == m_startNode; } inline void clear() { m_resultAccessRect = m_resultNeedRect = /*m_resultChangeRect =*/ m_childNeedRect = m_lastNeedRect = QRect(); m_needRectVaries = m_changeRectVaries = false; m_mergeTask.clear(); m_cloneNotifications.clear(); // Not needed really. Think over removing. //m_startNode = 0; //m_requestedRect = QRect(); } inline void pushJob(KisProjectionLeafSP leaf, NodePosition position, QRect applyRect) { JobItem item = {leaf, position, applyRect}; m_mergeTask.push(item); } inline QRect cropThisRect(const QRect& rect) { return m_cropRect.isValid() ? rect & m_cropRect : rect; } /** * Used by KisFullRefreshWalker as it has a special changeRect strategy */ inline void setExplicitChangeRect(KisProjectionLeafSP leaf, const QRect &changeRect, bool changeRectVaries) { m_resultChangeRect = changeRect; m_resultUncroppedChangeRect = changeRect; m_changeRectVaries = changeRectVaries; registerCloneNotification(leaf->node(), N_FILTHY); } /** * Called for every node we meet on a forward way of the trip. */ virtual void registerChangeRect(KisProjectionLeafSP leaf, NodePosition position) { // We do not work with masks here. It is KisLayer's job. if(!leaf->isLayer()) return; if(!(position & N_FILTHY) && !leaf->visible()) return; QRect currentChangeRect = leaf->projectionPlane()->changeRect(m_resultChangeRect, convertPositionToFilthy(position)); currentChangeRect = cropThisRect(currentChangeRect); if(!m_changeRectVaries) m_changeRectVaries = currentChangeRect != m_resultChangeRect; m_resultChangeRect = currentChangeRect; m_resultUncroppedChangeRect = leaf->projectionPlane()->changeRect(m_resultUncroppedChangeRect, convertPositionToFilthy(position)); registerCloneNotification(leaf->node(), position); } void registerCloneNotification(KisNodeSP node, NodePosition position) { /** * Note, we do not check for (N_ABOVE_FILTHY && * dependOnLowerNodes(node)) because it may lead to an * infinite loop with filter layer. Activate it when it is * guaranteed that it is not possible to create a filter layer * avobe its own clone */ if(hasClones(node) && position & (N_FILTHY | N_FILTHY_PROJECTION)) { m_cloneNotifications.append( CloneNotification(node, m_resultUncroppedChangeRect)); } } /** * Called for every node we meet on a backward way of the trip. */ virtual void registerNeedRect(KisProjectionLeafSP leaf, NodePosition position) { // We do not work with masks here. It is KisLayer's job. if(!leaf->isLayer()) return; if(m_mergeTask.isEmpty()) m_resultAccessRect = m_resultNeedRect = m_childNeedRect = m_lastNeedRect = m_resultChangeRect; QRect currentNeedRect; if(position & N_TOPMOST) m_lastNeedRect = m_childNeedRect; if (!leaf->visible()) { if (!m_lastNeedRect.isEmpty()) { // push a dumb job to fit state machine requirements pushJob(leaf, position, m_lastNeedRect); } } else if(position & (N_FILTHY | N_ABOVE_FILTHY | N_EXTRA)) { if(!m_lastNeedRect.isEmpty()) pushJob(leaf, position, m_lastNeedRect); //else /* Why push empty rect? */; m_resultAccessRect |= leaf->projectionPlane()->accessRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = leaf->projectionPlane()->needRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = cropThisRect(m_lastNeedRect); m_childNeedRect = m_lastNeedRect; } else if(position & (N_BELOW_FILTHY | N_FILTHY_PROJECTION)) { if(!m_lastNeedRect.isEmpty()) { pushJob(leaf, position, m_lastNeedRect); m_resultAccessRect |= leaf->projectionPlane()->accessRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = leaf->projectionPlane()->needRect(m_lastNeedRect, convertPositionToFilthy(position)); m_lastNeedRect = cropThisRect(m_lastNeedRect); } } else { // N_FILTHY_ORIGINAL is not used so it goes there qFatal("KisBaseRectsWalker: node position(%d) is out of range", position); } if(!m_needRectVaries) m_needRectVaries = m_resultNeedRect != m_lastNeedRect; m_resultNeedRect |= m_lastNeedRect; } virtual void adjustMasksChangeRect(KisProjectionLeafSP firstMask) { KisProjectionLeafSP currentLeaf = firstMask; while (currentLeaf) { /** * ATTENTION: we miss the first mask */ do { currentLeaf = currentLeaf->nextSibling(); } while (currentLeaf && (!currentLeaf->isMask() || !currentLeaf->visible())); if(currentLeaf) { QRect changeRect = currentLeaf->projectionPlane()->changeRect(m_resultChangeRect); m_changeRectVaries |= changeRect != m_resultChangeRect; m_resultChangeRect = changeRect; m_resultUncroppedChangeRect = changeRect; } } KisProjectionLeafSP parentLayer = firstMask->parent(); - Q_ASSERT(parentLayer); + KIS_SAFE_ASSERT_RECOVER_RETURN(parentLayer); registerCloneNotification(parentLayer->node(), N_FILTHY_PROJECTION); } static qint32 calculateChecksum(KisProjectionLeafSP leaf, const QRect &requestedRect) { qint32 checksum = 0; qint32 x, y, w, h; QRect tempRect; tempRect = leaf->projectionPlane()->changeRect(requestedRect); tempRect.getRect(&x, &y, &w, &h); checksum += -x - y + w + h; tempRect = leaf->projectionPlane()->needRect(requestedRect); tempRect.getRect(&x, &y, &w, &h); checksum += -x - y + w + h; // errKrita << leaf << requestedRect << "-->" << checksum; return checksum; } private: inline int getNodeLevelOfDetail(KisProjectionLeafSP leaf) { while (!leaf->projection()) { leaf = leaf->parent(); } KIS_ASSERT_RECOVER(leaf->projection()) { return 0; } return leaf->projection()->defaultBounds()->currentLevelOfDetail(); } private: /** * The result variables. * By the end of a recursion they will store a complete * data for a successful merge operation. */ QRect m_resultAccessRect; QRect m_resultNeedRect; QRect m_resultChangeRect; QRect m_resultUncroppedChangeRect; bool m_needRectVaries; bool m_changeRectVaries; LeafStack m_mergeTask; CloneNotificationsVector m_cloneNotifications; /** * Used by update optimization framework */ KisNodeSP m_startNode; QRect m_requestedRect; /** * Used for getting know whether the start node * properties have changed since the walker was * calculated */ qint32 m_nodeChecksum; /** * Used for getting know whether the structure of * the graph has changed since the walker was * calculated */ qint32 m_graphChecksum; /** * Temporary variables */ QRect m_cropRect; QRect m_childNeedRect; QRect m_lastNeedRect; int m_levelOfDetail; }; #endif /* __KIS_BASE_RECTS_WALKER_H */ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index f260490a8b..1e64bb7577 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1769 +1,1841 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here + KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask + KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(rhs.objectName()); m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; if (rhs.m_d->proofingConfig) { m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); if (exactCopy || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } }); } Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; + + /** + * The overlay device is not inherited when cloning the image! + */ + if (rhs.m_d->overlaySelectionMask) { + const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); + m_d->rootLayer->setDirty(dirtyRect); + } } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { emit sigInternalStopIsolatedModeRequested(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } +void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) +{ + if (m_d->targetOverlaySelectionMask == mask) return; + + m_d->targetOverlaySelectionMask = mask; + + struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { + UpdateOverlaySelectionStroke(KisImageSP image) + : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), + m_image(image) + { + this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); + setClearsRedoOnStart(false); + } + + void initStrokeCallback() { + KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; + KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; + if (oldMask == newMask) return; + + KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); + + m_image->m_d->overlaySelectionMask = newMask; + + if (oldMask || newMask) { + m_image->m_d->rootLayer->notifyChildMaskChanged(); + } + + if (oldMask) { + m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); + } + + if (newMask) { + newMask->setDirty(); + } + + m_image->undoAdapter()->emitSelectionChanged(); + } + + private: + KisImageSP m_image; + }; + + KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); + endStroke(id); +} + +KisSelectionMaskSP KisImage::overlaySelectionMask() const +{ + return m_d->overlaySelectionMask; +} + +bool KisImage::hasOverlaySelectionMask() const +{ + return m_d->overlaySelectionMask; +} + KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } - Q_ASSERT(m_d->rootLayer->childCount() > 0); - Q_ASSERT(m_d->rootLayer->selectionMask()); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, shearOrigin); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, shearOrigin); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } +KisNode *KisImage::graphOverlayNode() const +{ + return m_d->overlaySelectionMask.data(); +} + QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index fcea5fd28a..ba68762313 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1103 +1,1121 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_paint_device.h" // msvc cannot handle forward declarations, so include kis_paint_device here #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KisDocument; class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @param colorSpace can be null. in that case it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); ~KisImage() override; public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; + KisNode* graphOverlayNode() const override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; + /** + * Sets the mask (it must be a part of the node hierarchy already) to be paited on + * the top of all layers. This method does all the locking and syncing for you. It + * is executed asynchronously. + */ + void setOverlaySelectionMask(KisSelectionMaskSP mask); + + /** + * \see setOverlaySelectionMask + */ + KisSelectionMaskSP overlaySelectionMask() const; + + /** + * \see setOverlaySelectionMask + */ + bool hasOverlaySelectionMask() const; + /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param scaleX, @param scaleY scale coefficient to be applied to the node * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateNode(KisNodeSP node, double radians); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @param angleX, @param angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX, @param angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shearNode(KisNodeSP node, double angleX, double angleY); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * NOTE: Note conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * and executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is delted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return current level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. * * @param rc The rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. */ void disableUIUpdates() override; /** * \see disableUIUpdates */ void enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to dintinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_image_animation_interface.cpp b/libs/image/kis_image_animation_interface.cpp index cd97f3dbdf..3a0f6a3fe9 100644 --- a/libs/image/kis_image_animation_interface.cpp +++ b/libs/image/kis_image_animation_interface.cpp @@ -1,426 +1,425 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_animation_interface.h" #include #include "kis_global.h" #include "kis_image.h" #include "kis_regenerate_frame_stroke_strategy.h" #include "kis_switch_time_stroke_strategy.h" #include "kis_keyframe_channel.h" #include "kis_time_range.h" #include "kis_post_execution_undo_adapter.h" #include "commands_new/kis_switch_current_time_command.h" #include "kis_layer_utils.h" struct KisImageAnimationInterface::Private { Private() : image(0), externalFrameActive(false), frameInvalidationBlocked(false), cachedLastFrameValue(-1), audioChannelMuted(false), audioChannelVolume(0.5), m_currentTime(0), m_currentUITime(0) { } Private(const Private &rhs, KisImage *newImage) : image(newImage), externalFrameActive(false), frameInvalidationBlocked(false), fullClipRange(rhs.fullClipRange), playbackRange(rhs.playbackRange), framerate(rhs.framerate), cachedLastFrameValue(-1), audioChannelFileName(rhs.audioChannelFileName), audioChannelMuted(rhs.audioChannelMuted), audioChannelVolume(rhs.audioChannelVolume), m_currentTime(rhs.m_currentTime), m_currentUITime(rhs.m_currentUITime) { } KisImage *image; bool externalFrameActive; bool frameInvalidationBlocked; KisTimeRange fullClipRange; KisTimeRange playbackRange; int framerate; int cachedLastFrameValue; QString audioChannelFileName; bool audioChannelMuted; qreal audioChannelVolume; KisSwitchTimeStrokeStrategy::SharedTokenWSP switchToken; inline int currentTime() const { return m_currentTime; } inline int currentUITime() const { return m_currentUITime; } inline void setCurrentTime(int value) { m_currentTime = value; } inline void setCurrentUITime(int value) { m_currentUITime = value; } private: int m_currentTime; int m_currentUITime; }; KisImageAnimationInterface::KisImageAnimationInterface(KisImage *image) : QObject(image), m_d(new Private) { m_d->image = image; m_d->framerate = 24; m_d->fullClipRange = KisTimeRange::fromTime(0, 100); connect(this, SIGNAL(sigInternalRequestTimeSwitch(int, bool)), SLOT(switchCurrentTimeAsync(int, bool))); } KisImageAnimationInterface::KisImageAnimationInterface(const KisImageAnimationInterface &rhs, KisImage *newImage) : m_d(new Private(*rhs.m_d, newImage)) { connect(this, SIGNAL(sigInternalRequestTimeSwitch(int, bool)), SLOT(switchCurrentTimeAsync(int, bool))); } KisImageAnimationInterface::~KisImageAnimationInterface() { } bool KisImageAnimationInterface::hasAnimation() const { bool hasAnimation = false; KisLayerUtils::recursiveApplyNodes( m_d->image->root(), [&hasAnimation](KisNodeSP node) { hasAnimation |= node->isAnimated(); }); return hasAnimation; } int KisImageAnimationInterface::currentTime() const { return m_d->currentTime(); } int KisImageAnimationInterface::currentUITime() const { return m_d->currentUITime(); } const KisTimeRange& KisImageAnimationInterface::fullClipRange() const { return m_d->fullClipRange; } void KisImageAnimationInterface::setFullClipRange(const KisTimeRange range) { KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite()); m_d->fullClipRange = range; emit sigFullClipRangeChanged(); } void KisImageAnimationInterface::setFullClipRangeStartTime(int column) { KisTimeRange newRange(column, m_d->fullClipRange.end(), false); setFullClipRange(newRange); } void KisImageAnimationInterface::setFullClipRangeEndTime(int column) { KisTimeRange newRange(m_d->fullClipRange.start(), column, false); setFullClipRange(newRange); } const KisTimeRange& KisImageAnimationInterface::playbackRange() const { return m_d->playbackRange.isValid() ? m_d->playbackRange : m_d->fullClipRange; } void KisImageAnimationInterface::setPlaybackRange(const KisTimeRange range) { KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite()); m_d->playbackRange = range; emit sigPlaybackRangeChanged(); } int KisImageAnimationInterface::framerate() const { return m_d->framerate; } QString KisImageAnimationInterface::audioChannelFileName() const { return m_d->audioChannelFileName; } void KisImageAnimationInterface::setAudioChannelFileName(const QString &fileName) { QFileInfo info(fileName); KIS_SAFE_ASSERT_RECOVER_NOOP(fileName.isEmpty() || info.isAbsolute()); m_d->audioChannelFileName = fileName.isEmpty() ? fileName : info.absoluteFilePath(); emit sigAudioChannelChanged(); } bool KisImageAnimationInterface::isAudioMuted() const { return m_d->audioChannelMuted; } void KisImageAnimationInterface::setAudioMuted(bool value) { m_d->audioChannelMuted = value; emit sigAudioChannelChanged(); } qreal KisImageAnimationInterface::audioVolume() const { return m_d->audioChannelVolume; } void KisImageAnimationInterface::setAudioVolume(qreal value) { m_d->audioChannelVolume = value; emit sigAudioVolumeChanged(); } void KisImageAnimationInterface::setFramerate(int fps) { m_d->framerate = fps; emit sigFramerateChanged(); } KisImageWSP KisImageAnimationInterface::image() const { return m_d->image; } bool KisImageAnimationInterface::externalFrameActive() const { return m_d->externalFrameActive; } void KisImageAnimationInterface::requestTimeSwitchWithUndo(int time) { if (currentUITime() == time) return; requestTimeSwitchNonGUI(time, true); } void KisImageAnimationInterface::setDefaultProjectionColor(const KoColor &color) { int savedTime = 0; saveAndResetCurrentTime(currentTime(), &savedTime); m_d->image->setDefaultProjectionColor(color); restoreCurrentTime(&savedTime); } void KisImageAnimationInterface::requestTimeSwitchNonGUI(int time, bool useUndo) { emit sigInternalRequestTimeSwitch(time, useUndo); } void KisImageAnimationInterface::explicitlySetCurrentTime(int frameId) { m_d->setCurrentTime(frameId); } void KisImageAnimationInterface::switchCurrentTimeAsync(int frameId, bool useUndo) { if (currentUITime() == frameId) return; - KisTimeRange range = KisTimeRange::infinite(0); - KisTimeRange::calculateTimeRangeRecursive(m_d->image->root(), currentUITime(), range, true); - + const KisTimeRange range = KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), currentUITime()); const bool needsRegeneration = !range.contains(frameId); KisSwitchTimeStrokeStrategy::SharedTokenSP token = m_d->switchToken.toStrongRef(); if (!token || !token->tryResetDestinationTime(frameId, needsRegeneration)) { { KisPostExecutionUndoAdapter *undoAdapter = useUndo ? m_d->image->postExecutionUndoAdapter() : 0; KisSwitchTimeStrokeStrategy *strategy = new KisSwitchTimeStrokeStrategy(frameId, needsRegeneration, this, undoAdapter); m_d->switchToken = strategy->token(); KisStrokeId stroke = m_d->image->startStroke(strategy); m_d->image->endStroke(stroke); } if (needsRegeneration) { KisStrokeStrategy *strategy = new KisRegenerateFrameStrokeStrategy(this); KisStrokeId strokeId = m_d->image->startStroke(strategy); m_d->image->endStroke(strokeId); } } m_d->setCurrentUITime(frameId); emit sigUiTimeChanged(frameId); } void KisImageAnimationInterface::requestFrameRegeneration(int frameId, const QRegion &dirtyRegion) { KisStrokeStrategy *strategy = new KisRegenerateFrameStrokeStrategy(frameId, dirtyRegion, this); QList jobs = KisRegenerateFrameStrokeStrategy::createJobsData(m_d->image); KisStrokeId stroke = m_d->image->startStroke(strategy); Q_FOREACH (KisStrokeJobData* job, jobs) { m_d->image->addJob(stroke, job); } m_d->image->endStroke(stroke); } void KisImageAnimationInterface::saveAndResetCurrentTime(int frameId, int *savedValue) { m_d->externalFrameActive = true; *savedValue = m_d->currentTime(); m_d->setCurrentTime(frameId); } void KisImageAnimationInterface::restoreCurrentTime(int *savedValue) { m_d->setCurrentTime(*savedValue); m_d->externalFrameActive = false; } void KisImageAnimationInterface::notifyFrameReady() { emit sigFrameReady(m_d->currentTime()); } void KisImageAnimationInterface::notifyFrameCancelled() { emit sigFrameCancelled(); } KisUpdatesFacade* KisImageAnimationInterface::updatesFacade() const { return m_d->image; } void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node, const QRect &rect, bool recursive) { notifyNodeChanged(node, QVector({rect}), recursive); } void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node, const QVector &rects, bool recursive) { if (externalFrameActive() || m_d->frameInvalidationBlocked) return; - if (node->inherits("KisSelectionMask")) return; + // even overlay selection masks are not rendered in the cache + if (node->inherits("KisSelectionMask")) return; const int currentTime = m_d->currentTime(); KisTimeRange invalidateRange; if (recursive) { - KisTimeRange::calculateTimeRangeRecursive(node, currentTime, invalidateRange, false); + invalidateRange = KisTimeRange::calculateAffectedFramesRecursive(node, currentTime); } else { invalidateRange = KisTimeRange::calculateNodeAffectedFrames(node, currentTime); } // we compress the updated rect (atm, no one uses it anyway) QRect unitedRect; Q_FOREACH (const QRect &rc, rects) { unitedRect |= rc; } invalidateFrames(invalidateRange, unitedRect); } void KisImageAnimationInterface::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->cachedLastFrameValue = -1; emit sigFramesChanged(range, rect); } void KisImageAnimationInterface::blockFrameInvalidation(bool value) { m_d->frameInvalidationBlocked = value; } int findLastKeyframeTimeRecursive(KisNodeSP node) { int time = 0; KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { KisKeyframeSP keyframe = channel->lastKeyframe(); if (keyframe) { time = std::max(time, keyframe->time()); } } KisNodeSP child = node->firstChild(); while (child) { time = std::max(time, findLastKeyframeTimeRecursive(child)); child = child->nextSibling(); } return time; } int KisImageAnimationInterface::totalLength() { if (m_d->cachedLastFrameValue < 0) { m_d->cachedLastFrameValue = findLastKeyframeTimeRecursive(m_d->image->root()); } int lastKey = m_d->cachedLastFrameValue; lastKey = std::max(lastKey, m_d->fullClipRange.end()); lastKey = std::max(lastKey, m_d->currentUITime()); return lastKey + 1; } diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index d9c3c0a6f9..6144437fb0 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,591 +1,602 @@ /* * 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 "kis_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #ifdef Q_OS_OSX #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config(KSharedConfig::openConfig()->group(QString())) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); } #ifdef Q_OS_OSX // clear /var/folders/ swap path set by old broken Krita swap implementation in order to use new default swap dir. QString swap = m_config.readEntry("swaplocation", ""); if (swap.startsWith("/var/folders/")) { m_config.deleteEntry("swaplocation"); } #endif } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } QString KisImageConfig::safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const { #ifdef Q_OS_OSX // On OSX, QDir::tempPath() gives us a folder we cannot reply upon (usually // something like /var/folders/.../...) and that will have vanished when we // try to create the tmp file in KisMemoryWindow::KisMemoryWindow using // swapFileTemplate. thus, we just pick the home folder if swapDir does not // tell us otherwise. // the other option here would be to use a "garbled name" temp file (i.e. no name // KRITA_SWAP_FILE_XXXXXX) in an obscure /var/folders place, which is not // nice to the user. having a clearly named swap file in the home folder is // much nicer to Krita's users. // furthermore, this is just a default and swapDir can always be configured // to another location. QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QDir::separator() + suffix; #else Q_UNUSED(suffix); QString swap = QDir::tempPath(); #endif if (requestDefault) { return swap; } const QString configuredSwap = m_config.readEntry(configKey, swap); if (!configuredSwap.isEmpty()) { swap = configuredSwap; } return swap; } QString KisImageConfig::swapDir(bool requestDefault) { return safelyGetWritableTempLocation("swap", "swaplocation", requestDefault); } void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } void KisImageConfig::setLazyFrameCreationEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_OSX #include #include #endif #include int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_OSX int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } QColor def(Qt::green); m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } int KisImageConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_config.readEntry("maxNumberOfThreads", QThread::idealThreadCount())); } void KisImageConfig::setMaxNumberOfThreads(int value) { if (value == QThread::idealThreadCount()) { m_config.deleteEntry("maxNumberOfThreads"); } else { m_config.writeEntry("maxNumberOfThreads", value); } } int KisImageConfig::frameRenderingClones(bool defaultValue) const { const int defaultClonesCount = qMax(1, maxNumberOfThreads(defaultValue) / 2); return defaultValue ? defaultClonesCount : m_config.readEntry("frameRenderingClones", defaultClonesCount); } void KisImageConfig::setFrameRenderingClones(int value) { m_config.writeEntry("frameRenderingClones", value); } int KisImageConfig::fpsLimit(bool defaultValue) const { return defaultValue ? 100 : m_config.readEntry("fpsLimit", 100); } void KisImageConfig::setFpsLimit(int value) { m_config.writeEntry("fpsLimit", value); } bool KisImageConfig::useOnDiskAnimationCacheSwapping(bool defaultValue) const { return defaultValue ? true : m_config.readEntry("useOnDiskAnimationCacheSwapping", true); } void KisImageConfig::setUseOnDiskAnimationCacheSwapping(bool value) { m_config.writeEntry("useOnDiskAnimationCacheSwapping", value); } QString KisImageConfig::animationCacheDir(bool defaultValue) const { return safelyGetWritableTempLocation("animation_cache", "animationCacheDir", defaultValue); } void KisImageConfig::setAnimationCacheDir(const QString &value) { m_config.writeEntry("animationCacheDir", value); } bool KisImageConfig::useAnimationCacheFrameSizeLimit(bool defaultValue) const { return defaultValue ? true : m_config.readEntry("useAnimationCacheFrameSizeLimit", true); } void KisImageConfig::setUseAnimationCacheFrameSizeLimit(bool value) { m_config.writeEntry("useAnimationCacheFrameSizeLimit", value); } int KisImageConfig::animationCacheFrameSizeLimit(bool defaultValue) const { return defaultValue ? 2500 : m_config.readEntry("animationCacheFrameSizeLimit", 2500); } void KisImageConfig::setAnimationCacheFrameSizeLimit(int value) { m_config.writeEntry("animationCacheFrameSizeLimit", value); } bool KisImageConfig::useAnimationCacheRegionOfInterest(bool defaultValue) const { return defaultValue ? true : m_config.readEntry("useAnimationCacheRegionOfInterest", true); } void KisImageConfig::setUseAnimationCacheRegionOfInterest(bool value) { m_config.writeEntry("useAnimationCacheRegionOfInterest", value); } qreal KisImageConfig::animationCacheRegionOfInterestMargin(bool defaultValue) const { return defaultValue ? 0.25 : m_config.readEntry("animationCacheRegionOfInterestMargin", 0.25); } void KisImageConfig::setAnimationCacheRegionOfInterestMargin(qreal value) { m_config.writeEntry("animationCacheRegionOfInterestMargin", value); } + +QColor KisImageConfig::selectionOverlayMaskColor(bool defaultValue) const +{ + QColor def(255, 0, 0, 220); + return (defaultValue ? def : m_config.readEntry("selectionOverlayMaskColor", def)); +} + +void KisImageConfig::setSelectionOverlayMaskColor(const QColor &color) +{ + m_config.writeEntry("selectionOverlayMaskColor", color); +} diff --git a/libs/image/kis_image_config.h b/libs/image/kis_image_config.h index f338def039..b3ec8b797e 100644 --- a/libs/image/kis_image_config.h +++ b/libs/image/kis_image_config.h @@ -1,153 +1,156 @@ /* * 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. */ #ifndef KIS_IMAGE_CONFIG_H_ #define KIS_IMAGE_CONFIG_H_ #include #include "kritaimage_export.h" #include "KisProofingConfiguration.h" #include "kis_types.h" class KRITAIMAGE_EXPORT KisImageConfig { public: KisImageConfig(bool readOnly); ~KisImageConfig(); bool enableProgressReporting(bool requestDefault = false) const; void setEnableProgressReporting(bool value); bool enablePerfLog(bool requestDefault = false) const; void setEnablePerfLog(bool value); qreal transformMaskOffBoundsReadArea() const; int updatePatchHeight() const; void setUpdatePatchHeight(int value); int updatePatchWidth() const; void setUpdatePatchWidth(int value); qreal maxCollectAlpha() const; qreal maxMergeAlpha() const; qreal maxMergeCollectAlpha() const; qreal schedulerBalancingRatio() const; void setSchedulerBalancingRatio(qreal value); int maxSwapSize(bool requestDefault = false) const; void setMaxSwapSize(int value); int swapSlabSize() const; void setSwapSlabSize(int value); int swapWindowSize() const; void setSwapWindowSize(int value); int tilesHardLimit() const; // MiB int tilesSoftLimit() const; // MiB int poolLimit() const; // MiB qreal memoryHardLimitPercent(bool requestDefault = false) const; // % of total RAM qreal memorySoftLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() * (1 - 0.01 * memoryPoolLimitPercent()) qreal memoryPoolLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() void setMemoryHardLimitPercent(qreal value); void setMemorySoftLimitPercent(qreal value); void setMemoryPoolLimitPercent(qreal value); static int totalRAM(); // MiB /** * @return a specific directory for the swapfile, if set. If not set, return an * empty QString and use the default KDE directory. */ QString swapDir(bool requestDefault = false); void setSwapDir(const QString &swapDir); int numberOfOnionSkins() const; void setNumberOfOnionSkins(int value); int onionSkinTintFactor() const; void setOnionSkinTintFactor(int value); int onionSkinOpacity(int offset) const; void setOnionSkinOpacity(int offset, int value); bool onionSkinState(int offset) const; void setOnionSkinState(int offset, bool value); QColor onionSkinTintColorBackward() const; void setOnionSkinTintColorBackward(const QColor &value); QColor onionSkinTintColorForward() const; void setOnionSkinTintColorForward(const QColor &value); bool lazyFrameCreationEnabled(bool requestDefault = false) const; void setLazyFrameCreationEnabled(bool value); bool showAdditionalOnionSkinsSettings(bool requestDefault = false) const; void setShowAdditionalOnionSkinsSettings(bool value); int defaultFrameColorLabel() const; void setDefaultFrameColorLabel(int label); KisProofingConfigurationSP defaultProofingconfiguration(); void setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState); bool useLodForColorizeMask(bool requestDefault = false) const; void setUseLodForColorizeMask(bool value); int maxNumberOfThreads(bool defaultValue = false) const; void setMaxNumberOfThreads(int value); int frameRenderingClones(bool defaultValue = false) const; void setFrameRenderingClones(int value); int fpsLimit(bool defaultValue = false) const; void setFpsLimit(int value); bool useOnDiskAnimationCacheSwapping(bool defaultValue = false) const; void setUseOnDiskAnimationCacheSwapping(bool value); QString animationCacheDir(bool defaultValue = false) const; void setAnimationCacheDir(const QString &value); bool useAnimationCacheFrameSizeLimit(bool defaultValue = false) const; void setUseAnimationCacheFrameSizeLimit(bool value); int animationCacheFrameSizeLimit(bool defaultValue = false) const; void setAnimationCacheFrameSizeLimit(int value); bool useAnimationCacheRegionOfInterest(bool defaultValue = false) const; void setUseAnimationCacheRegionOfInterest(bool value); qreal animationCacheRegionOfInterestMargin(bool defaultValue = false) const; void setAnimationCacheRegionOfInterestMargin(qreal value); + QColor selectionOverlayMaskColor(bool defaultValue = false) const; + void setSelectionOverlayMaskColor(const QColor &color); + private: Q_DISABLE_COPY(KisImageConfig) QString safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const; private: KConfigGroup m_config; bool m_readOnly; }; #endif /* KIS_IMAGE_CONFIG_H_ */ diff --git a/libs/image/kis_image_signal_router.cpp b/libs/image/kis_image_signal_router.cpp index 284e735713..bfa3486b1e 100644 --- a/libs/image/kis_image_signal_router.cpp +++ b/libs/image/kis_image_signal_router.cpp @@ -1,158 +1,160 @@ /* * 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.h" #include #include "kis_image.h" #define CONNECT_TO_IMAGE(signal) \ connect(this, SIGNAL(signal), m_image, SIGNAL(signal), Qt::DirectConnection) struct ImageSignalsStaticRegistrar { ImageSignalsStaticRegistrar() { qRegisterMetaType("KisImageSignalType"); } }; static ImageSignalsStaticRegistrar __registrar; KisImageSignalRouter::KisImageSignalRouter(KisImageWSP image) : QObject(image.data()), m_image(image) { connect(this, SIGNAL(sigNotification(KisImageSignalType)), SLOT(slotNotification(KisImageSignalType))); CONNECT_TO_IMAGE(sigImageModified()); CONNECT_TO_IMAGE(sigSizeChanged(const QPointF&, const QPointF&)); CONNECT_TO_IMAGE(sigProfileChanged(const KoColorProfile*)); CONNECT_TO_IMAGE(sigColorSpaceChanged(const KoColorSpace*)); CONNECT_TO_IMAGE(sigResolutionChanged(double, double)); CONNECT_TO_IMAGE(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)); CONNECT_TO_IMAGE(sigNodeChanged(KisNodeSP)); CONNECT_TO_IMAGE(sigNodeAddedAsync(KisNodeSP)); CONNECT_TO_IMAGE(sigRemoveNodeAsync(KisNodeSP)); CONNECT_TO_IMAGE(sigLayersChangedAsync()); } KisImageSignalRouter::~KisImageSignalRouter() { } void KisImageSignalRouter::emitNotifications(KisImageSignalVector notifications) { Q_FOREACH (const KisImageSignalType &type, notifications) { emitNotification(type); } } void KisImageSignalRouter::emitNotification(KisImageSignalType type) { /** * All the notifications except LayersChangedSignal should go in a * queued way. And LayersChangedSignal should be delivered to the * recipients in a non-reordered way */ if (type.id == LayersChangedSignal) { slotNotification(type); } else { emit sigNotification(type); } } void KisImageSignalRouter::emitNodeChanged(KisNodeSP node) { emit sigNodeChanged(node); } void KisImageSignalRouter::emitNodeHasBeenAdded(KisNode *parent, int index) { KisNodeSP newNode = parent->at(index); + // overlay selection masks reset frames themselves if (!newNode->inherits("KisSelectionMask")) { KisImageSP image = m_image.toStrongRef(); if (image) { image->invalidateAllFrames(); } } emit sigNodeAddedAsync(newNode); } void KisImageSignalRouter::emitAboutToRemoveANode(KisNode *parent, int index) { KisNodeSP removedNode = parent->at(index); + // overlay selection masks reset frames themselves if (!removedNode->inherits("KisSelectionMask")) { KisImageSP image = m_image.toStrongRef(); if (image) { image->invalidateAllFrames(); } } emit sigRemoveNodeAsync(removedNode); } void KisImageSignalRouter::slotNotification(KisImageSignalType type) { KisImageSP image = m_image.toStrongRef(); if (!image) { return; } switch(type.id) { case LayersChangedSignal: image->invalidateAllFrames(); emit sigLayersChangedAsync(); break; case ModifiedSignal: emit sigImageModified(); break; case SizeChangedSignal: image->invalidateAllFrames(); emit sigSizeChanged(type.sizeChangedSignal.oldStillPoint, type.sizeChangedSignal.newStillPoint); break; case ProfileChangedSignal: image->invalidateAllFrames(); emit sigProfileChanged(image->profile()); break; case ColorSpaceChangedSignal: image->invalidateAllFrames(); emit sigColorSpaceChanged(image->colorSpace()); break; case ResolutionChangedSignal: image->invalidateAllFrames(); emit sigResolutionChanged(image->xRes(), image->yRes()); break; case NodeReselectionRequestSignal: if (type.nodeReselectionSignal.newActiveNode || !type.nodeReselectionSignal.newSelectedNodes.isEmpty()) { emit sigRequestNodeReselection(type.nodeReselectionSignal.newActiveNode, type.nodeReselectionSignal.newSelectedNodes); } break; } } diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index b39306a92f..cc945da37c 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,973 +1,1034 @@ /* * 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_layer.h" #include #include #include #include #include #include +#include +#include +#include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" +#include "kis_projection_leaf.h" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } return m_projection; } void tryCopyFrom(const KisSafeProjection &rhs) { QMutexLocker locker(&m_lock); if (!rhs.m_projection) return; if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*rhs.m_projection); m_projection = m_reusablePaintDevice; } else { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(rhs.m_projection, rhs.m_projection->extent()); } } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; +class KisLayerMasksCache { +public: + KisLayerMasksCache(KisLayer *parent) + : m_parent(parent) + { + } + + KisSelectionMaskSP selectionMask() { + QReadLocker readLock(&m_lock); + + if (!m_isSelectionMaskValid) { + readLock.unlock(); + + QWriteLocker writeLock(&m_lock); + if (!m_isSelectionMaskValid) { + KoProperties properties; + properties.setProperty("active", true); + properties.setProperty("visible", true); + QList masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties); + + // return the first visible mask + Q_FOREACH (KisNodeSP mask, masks) { + if (mask) { + m_selectionMask = dynamic_cast(mask.data()); + break; + } + } + m_isSelectionMaskValid = true; + } + + // return under write lock + return m_selectionMask; + } + + // return under read lock + return m_selectionMask; + } + + QList effectMasks() { + QReadLocker readLock(&m_lock); + + if (!m_isEffectMasksValid) { + readLock.unlock(); + + QWriteLocker writeLock(&m_lock); + if (!m_isEffectMasksValid) { + m_effectMasks = m_parent->searchEffectMasks(0); + m_isEffectMasksValid = true; + } + + // return under write lock + return m_effectMasks; + } + + // return under read lock + return m_effectMasks; + } + + void setDirty() + { + QWriteLocker l(&m_lock); + m_isSelectionMaskValid = false; + m_isEffectMasksValid = false; + m_selectionMask = 0; + m_effectMasks.clear(); + } + +private: + KisLayer *m_parent; + + QReadWriteLock m_lock; + + bool m_isSelectionMaskValid = false; + bool m_isEffectMasksValid = false; + KisSelectionMaskSP m_selectionMask; + QList m_effectMasks; +}; + struct Q_DECL_HIDDEN KisLayer::Private { + Private(KisLayer *q) : masksCache(q) {} + KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; - KisSelectionMaskSP selectionMask; - QList effectMasks; + KisLayerMasksCache masksCache; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() - , m_d(new Private) + , m_d(new Private(this)) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); - notifyChildMaskChanged(KisNodeSP()); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) - , m_d(new Private()) + , m_d(new Private(this)) { if (this != &rhs) { m_d->image = rhs.m_d->image; m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; m_d->safeProjection.tryCopyFrom(rhs.m_d->safeProjection); setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone(); KIS_SAFE_ASSERT_RECOVER_NOOP(rhs.m_d->layerStyleProjectionPlane); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } - notifyChildMaskChanged(KisNodeSP()); } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = m_d->image.toStrongRef(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } KisNodeSP node = firstChild(); while (node) { KisLayerUtils::recursiveApplyNodes(node, [image] (KisNodeSP node) { node->setImage(image); }); node = node->nextSibling(); } } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } -void KisLayer::notifyChildMaskChanged(KisNodeSP changedChildMask) +void KisLayer::notifyChildMaskChanged() { - Q_UNUSED(changedChildMask); - - updateSelectionMask(); - updateEffectMasks(); + m_d->masksCache.setDirty(); } KisSelectionMaskSP KisLayer::selectionMask() const { - return m_d->selectionMask; -} - -void KisLayer::updateSelectionMask() -{ - KoProperties properties; - properties.setProperty("active", true); - properties.setProperty("visible", true); - QList masks = childNodes(QStringList("KisSelectionMask"), properties); - - // return the first visible mask - Q_FOREACH (KisNodeSP mask, masks) { - if (mask) { - m_d->selectionMask = dynamic_cast(mask.data()); - return; - } - } - m_d->selectionMask = KisSelectionMaskSP(); + return m_d->masksCache.selectionMask(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = m_d->image.toStrongRef(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// -const QList &KisLayer::effectMasks() const +QList KisLayer::effectMasks() const { - return m_d->effectMasks; + return m_d->masksCache.effectMasks(); } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } -void KisLayer::updateEffectMasks() -{ - m_d->effectMasks = searchEffectMasks(KisNodeSP()); -} - QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; - if (childCount() > 0) { - KoProperties properties; - properties.setProperty("visible", true); - QList nodes = childNodes(QStringList("KisEffectMask"), properties); + KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf()); + + KisProjectionLeafSP child = projectionLeaf()->firstChild(); + while (child) { + if (child->node() == lastNode) break; - Q_FOREACH (const KisNodeSP& node, nodes) { - if (node == lastNode) break; + KIS_SAFE_ASSERT_RECOVER_NOOP(child); + KIS_SAFE_ASSERT_RECOVER_NOOP(child->node()); - KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); - if (mask) + if (child->visible()) { + KisEffectMaskSP mask = dynamic_cast(const_cast(child->node().data())); + if (mask) { masks.append(mask); + } } + + child = child->nextSibling(); } return masks; } bool KisLayer::hasEffectMasks() const { - return !m_d->effectMasks.empty(); + return !m_d->masksCache.effectMasks().isEmpty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection.getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { - notifyChildMaskChanged(changedChildNode); + notifyChildMaskChanged(); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); if (originalDevice) { KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); if (channel) { KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); KisKeyframeSP keyframe = channel->activeKeyframeAt(time); channel->fetchFrame(keyframe, targetDevice); return targetDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } return createThumbnail(w, h); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_layer.h b/libs/image/kis_layer.h index 51177728a8..8149963f46 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,408 +1,405 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * 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. */ #ifndef KIS_LAYER_H_ #define KIS_LAYER_H_ #include #include #include #include #include "kritaimage_export.h" #include "kis_base_node.h" #include "kis_types.h" #include "kis_node.h" #include "kis_psd_layer_style.h" template class QStack; class QBitArray; class KisCloneLayer; class KisPSDLayerStyle; class KisAbstractProjectionPlane; namespace KisMetaData { class Store; } /** * Abstract class that represents the concept of a Layer in Krita. This is not related * to the paint devices: this is merely an abstraction of how layers can be stacked and * rendered differently. * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer * is at the top of the group in the layerlist, using next will iterate to the bottom to last, * whereas previous will go up to first again. * * * TODO: Add a layer mode whereby the projection of the layer is used * as a clipping path? **/ class KRITAIMAGE_EXPORT KisLayer : public KisNode { Q_OBJECT public: /** * @param image is the pointer of the image or null * @param opacity is a value between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 **/ KisLayer(KisImageWSP image, const QString &name, quint8 opacity); KisLayer(const KisLayer& rhs); ~KisLayer() override; /// returns the image's colorSpace or null, if there is no image const KoColorSpace * colorSpace() const override; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const override; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ KisAbstractProjectionPlaneSP projectionPlane() const override; /** * The projection plane representing the layer itself without any * styles or anything else. It is used by the layer styles projection * plane to stack up the planes. */ virtual KisAbstractProjectionPlaneSP internalProjectionPlane() const; QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect); void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect); virtual bool needProjection() const; /** * Return the fully rendered representation of this layer: its * data and its effect masks */ KisPaintDeviceSP projection() const override; /** * Return the layer data before the effect masks have had their go * at it. */ KisPaintDeviceSP original() const override = 0; /** * @return the selection associated with this layer, if there is * one. Otherwise, return 0; */ virtual KisSelectionMaskSP selectionMask() const; /** * @return the selection contained in the first KisSelectionMask associated * with this layer or the image, if either exists, otherwise, return 0. */ virtual KisSelectionSP selection() const; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; /** * set/unset the channel flag for the alpha channel of this layer */ void disableAlphaChannel(bool disable); /** * returns true if the channel flag for the alpha channel * of this layer is not set. * returns false otherwise. */ bool alphaChannelDisabled() const; /** * set the channelflags for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ virtual void setChannelFlags(const QBitArray & channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is active or not. If the channelflags bit * array is empty, all channels are active. */ QBitArray & channelFlags() const; /** * Returns true if this layer is temporary: i.e., it should not * appear in the layerbox, even though it is temporarily in the * layer stack and taken into account on recomposition. */ bool temporary() const; /** * Set to true if this layer should not appear in the layerbox, * even though it is temporarily in the layer stack and taken into * account on recomposition. */ void setTemporary(bool t); /// returns the image this layer belongs to, or null if there is no image KisImageWSP image() const; /** * Set the image this layer belongs to. */ void setImage(KisImageWSP image) override; /** * Create and return a layer that is the result of merging * this with layer. * * This method is designed to be called only within KisImage::mergeLayerDown(). * * Decendands override this to create specific merged types when possible. * The KisLayer one creates a KisPaintLayerSP via a bitBlt, and can work on all layer types. * * Descendants that perform their own version do NOT call KisLayer::createMergedLayer */ virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clones should be informed about updates of the original * layer, so this is a way to register them */ void registerClone(KisCloneLayerWSP clone); /** * Deregisters the clone from the update list * * \see registerClone() */ void unregisterClone(KisCloneLayerWSP clone); /** * Return the list of the clones of this node. Be careful * with the list, because it is not thread safe. */ const QList registeredClones() const; /** * Returns whether we have a clone. * * Be careful with it. It is not thread safe to add/remove * clone while checking hasClones(). So there should be no updates. */ bool hasClones() const; /** * It is calles by the async merger after projection update is done */ void updateClones(const QRect &rect); /** * Informs this layers that its masks might have changed. */ - void notifyChildMaskChanged(KisNodeSP changedChildMask); + void notifyChildMaskChanged(); public: qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const override; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const override; QImage createThumbnail(qint32 w, qint32 h) override; QImage createThumbnailForFrame(qint32 w, qint32 h, int time) override; public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ - const QList &effectMasks() const; + QList effectMasks() const; /** * @return the list of effect masks up to a certain node */ QList effectMasks(KisNodeSP lastNode) const; /** * Get the group layer that contains this layer. */ KisLayerSP parentLayer() const; /** * @return the metadata object associated with this object. */ KisMetaData::Store* metaData(); protected: // override from KisNode QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; void childNodeChanged(KisNodeSP changedChildNode) override; protected: /** * Ask the layer to assemble its data & apply all the effect masks * to it. */ QRect updateProjection(const QRect& rect, KisNodeSP filthyNode); /** * Layers can override this method to get some special behavior * when copying data from \p original to \p projection, e.g. blend * in indirect painting device. If you need to modify data * outside \p rect, please also override outgoingChangeRect() * method. */ virtual void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; /** * For KisLayer classes change rect transformation consists of two * parts: incoming and outgoing. * * 1) incomingChangeRect(rect) chande rect transformation * performed by the transformations done basing on global * projection. It is performed in KisAsyncMerger + * KisUpdateOriginalVisitor classes. It happens before data * coming to KisLayer::original() therefore it is * 'incoming'. See KisAdjustmentLayer for example of usage. * * 2) outgoingChangeRect(rect) change rect transformation that * happens in KisLayer::copyOriginalToProjection(). It applies * *only* when the layer is 'filthy', that is was the cause of * the merge process. See KisCloneLayer for example of usage. * * The flow of changed areas can be illustrated in the * following way: * * 1. Current projection of size R1 is stored in KisAsyncMerger::m_currentProjection * | * | <-- KisUpdateOriginalVisitor writes data into layer's original() device. * | The changed area on KisLayer::original() is * | R2 = KisLayer::incomingChangeRect(R1) * | * 2. KisLayer::original() / changed rect: R2 * | * | <-- KisLayer::updateProjection() starts composing a layer * | It calls KisLayer::copyOriginalToProjection() which copies some area * | to a temporaty device. The temporary device now stores * | R3 = KisLayer::outgoingChangeRect(R2) * | * 3. Temporary device / changed rect: R3 * | * | <-- KisLayer::updateProjection() continues composing a layer. It merges a mask. * | R4 = KisMask::changeRect(R3) * | * 4. KisLayer::original() / changed rect: R4 * * So in the end rect R4 will be passed up to the next layers in the stack. */ virtual QRect incomingChangeRect(const QRect &rect) const; /** * \see incomingChangeRect() */ virtual QRect outgoingChangeRect(const QRect &rect) const; /** * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return an area that should be updated because of * the change of @requestedRect of the layer */ QRect masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const; /** * Get needRects for all masks * @param changeRect requested rect to be updated on final * projection. Should be a return value * of @ref masksChangedRect() * @param applyRects (out param) a stack of the rects where filters * should be applied * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return a needRect that should be prepared on the layer's * paintDevice for all masks to succeed */ QRect masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const; QRect applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const; bool canMergeAndKeepBlendOptions(KisLayerSP otherLayer); - void updateSelectionMask(); - - void updateEffectMasks(); - QList searchEffectMasks(KisNodeSP lastNode) const; private: + friend class KisLayerMasksCache; friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: QRect layerExtentImpl(bool exactBounds) const; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index e456667662..362078252e 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1470 +1,1459 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_utils.h" #include #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "commands/kis_image_change_visibility_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include "commands/kis_node_compositeop_command.h" #include #include "krita_utils.h" namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = qobject_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; bool useInTimeline = false; bool enableOnionSkins = false; virtual KisNodeList allSrcNodes() = 0; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(currLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); paintLayer = qobject_cast(prevLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); useInTimeline |= node->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(node.data()); if (paintLayer) { enableOnionSkins |= paintLayer->onionSkinEnabled(); } } } KisNodeList mergedNodes; bool nodesCompositingVaries = false; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand { DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { /** * We disable extra compositing only in case all the layers have * the same compositing properties, therefore, we can just sum them using * Normal blend mode */ if (m_info->nodesCompositingVaries) return; // we should disable dirty requests on **redo only**, otherwise // the state of the layers will not be recovered on undo m_info->image->disableDirtyRequests(); Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (node->compositeOpId() != COMPOSITE_OVER) { addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER)); } if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::inheritAlpha, false); addCommand(new KisNodePropertyListCommand(node, props)); } } m_info->image->enableDirtyRequests(); } private: MergeMultipleInfoSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { forceAllDelayedNodesUpdate(node); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } m_info->dstNode->setUseInTimeline(m_info->useInTimeline); KisPaintLayer *dstPaintLayer = qobject_cast(m_info->dstNode.data()); if (dstPaintLayer) { dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); m_info->dstNode = dstPaintLayer; if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } auto channelFlagsLazy = [](KisNodeSP node) { KisLayer *layer = dynamic_cast(node.data()); return layer ? layer->channelFlags() : QBitArray(); }; QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; bool isFirstCycle = true; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (isFirstCycle) { compositeOpId = node->compositeOpId(); channelFlags = channelFlagsLazy(node); isFirstCycle = false; } else if (compositeOpId != node->compositeOpId() || channelFlags != channelFlagsLazy(node)) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } m_info->nodesCompositingVaries = compositionVaries; m_info->dstNode->setUseInTimeline(m_info->useInTimeline); dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::end() { KisImageSignalType type; if (isFinalizing()) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } KisLayerSP constructDefaultLayer(KisImageSP image) { return new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, false, true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = constructDefaultLayer(image); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node -- checking // whether the parent is going to be deleted parent = putAfter->parent(); while (parent && nodesToDelete.contains(parent)) { parent = parent->parent(); } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { KisNodeSP oldRoot = m_info->image->root(); KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { if (parent == m_putAfter->parent()) { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, true, false)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, parent->lastChild(), true, false)); } /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } KisNodeList safeNodesToDelete = m_info->allSrcNodes(); for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) { KisNodeSP node = *it; if (node->userLocked() && node->visible()) { addCommand(new KisImageChangeVisibilityCommand(false, node)); } } KritaUtils::filterContainer(safeNodesToDelete, [this](KisNodeSP node) { return !node->userLocked(); }); safeRemoveMultipleNodes(safeNodesToDelete, m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::init() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::end() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd); applyKeyframeColorLabel(keyframe); addCommand(cmd); } void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) { Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) { Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisKeyframeSP keyframe = channel->keyframeAt(m_frame); if (!keyframe.isNull() && keyframe->colorLabel() != 0) { dstKeyframe->setColorLabel(keyframe->colorLabel()); return; } } } dstKeyframe->setColorLabel(0); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // NOTE: shape layer may have emitted spontaneous jobs during layer creation, // wait for them to complete! applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass trhough only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { //dbgImage << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible() || node->userLocked()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); applicator.applyCommand(new DisableExtraCompositing(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, 0, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::end() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } - void recursiveApplyNodes(KisNodeSP node, std::function func) - { - func(node); - - node = node->firstChild(); - while (node) { - recursiveApplyNodes(node, func); - node = node->nextSibling(); - } - } - KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } void forceAllDelayedNodesUpdate(KisNodeSP root) { KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } } diff --git a/libs/image/kis_layer_utils.h b/libs/image/kis_layer_utils.h index 3eb41ffa99..4020f7e21e 100644 --- a/libs/image/kis_layer_utils.h +++ b/libs/image/kis_layer_utils.h @@ -1,205 +1,216 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LAYER_UTILS_H #define __KIS_LAYER_UTILS_H #include #include "kundo2command.h" #include "kis_types.h" #include "kritaimage_export.h" #include "kis_command_utils.h" class KoProperties; class KoColor; class QUuid; namespace KisMetaData { class MergeStrategy; } namespace KisLayerUtils { KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList &inputNodes, QList &outputNodes); KRITAIMAGE_EXPORT KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes); KRITAIMAGE_EXPORT void filterMergableNodes(KisNodeList &nodes, bool allowMasks = false); KRITAIMAGE_EXPORT bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents); /** * Returns true if: * o \p node is a clone of some layer in \p nodes * o \p node is a clone any child layer of any layer in \p nodes * o \p node is a clone of a clone of a ..., that in the end points * to any layer in \p nodes of their children. */ KRITAIMAGE_EXPORT bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes); KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root); KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false); KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy); KRITAIMAGE_EXPORT QSet fetchLayerFrames(KisNodeSP node); KRITAIMAGE_EXPORT QSet fetchLayerFramesRecursive(KisNodeSP rootNode); KRITAIMAGE_EXPORT void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter); KRITAIMAGE_EXPORT void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter); KRITAIMAGE_EXPORT bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter); KRITAIMAGE_EXPORT void flattenLayer(KisImageSP image, KisLayerSP layer); KRITAIMAGE_EXPORT void flattenImage(KisImageSP image); KRITAIMAGE_EXPORT void addCopyOfNameTag(KisNodeSP node); KRITAIMAGE_EXPORT KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot); KRITAIMAGE_EXPORT void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color); typedef QMap > FrameJobs; void updateFrameJobs(FrameJobs *jobs, KisNodeSP node); void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode); struct SwitchFrameCommand : public KisCommandUtils::FlipFlopCommand { struct SharedStorage { /** * For some reason the absence of a destructor in the SharedStorage * makes Krita crash on exit. Seems like some compiler weirdness... (DK) */ ~SharedStorage(); int value; }; typedef QSharedPointer SharedStorageSP; public: SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage); ~SwitchFrameCommand() override; private: void init() override; void end() override; private: KisImageWSP m_image; int m_newTime; SharedStorageSP m_storage; }; /** * A command to keep correct set of selected/active nodes thoroughout * the action. */ class KRITAIMAGE_EXPORT KeepNodesSelectedCommand : public KisCommandUtils::FlipFlopCommand { public: KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent = 0); void end() override; private: KisNodeList m_selectedBefore; KisNodeList m_selectedAfter; KisNodeSP m_activeBefore; KisNodeSP m_activeAfter; KisImageWSP m_image; }; KRITAIMAGE_EXPORT KisLayerSP constructDefaultLayer(KisImageSP image); class KRITAIMAGE_EXPORT RemoveNodeHelper { public: virtual ~RemoveNodeHelper(); protected: virtual void addCommandImpl(KUndo2Command *cmd) = 0; void safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image); private: bool checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes); static bool scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove); }; struct SimpleRemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand { SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image); void populateChildCommands() override; protected: void addCommandImpl(KUndo2Command *cmd) override; private: KisNodeList m_nodes; KisImageSP m_image; KisNodeList m_selectedNodes; KisNodeSP m_activeNode; }; class KRITAIMAGE_EXPORT KisSimpleUpdateCommand : public KisCommandUtils::FlipFlopCommand { public: KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent = 0); void end() override; static void updateNodes(const KisNodeList &nodes); private: KisNodeList m_nodes; }; template bool checkNodesDiffer(KisNodeList nodes, std::function checkerFunc) { bool valueDiffers = false; bool initialized = false; T currentValue = T(); Q_FOREACH (KisNodeSP node, nodes) { if (!initialized) { currentValue = checkerFunc(node); initialized = true; } else if (currentValue != checkerFunc(node)) { valueDiffers = true; break; } } return valueDiffers; } /** * Applies \p func to \p node and all its children recursively */ - void KRITAIMAGE_EXPORT recursiveApplyNodes(KisNodeSP node, std::function func); + template + void recursiveApplyNodes(NodePointer node, Functor func) + { + func(node); + + node = node->firstChild(); + while (node) { + recursiveApplyNodes(node, func); + node = node->nextSibling(); + } + } + /** * Walks through \p node and all its children recursively until * \p func returns true. When \p func returns true, the node is * considered to be found, the search is stopped and the found * node is returned to the caller. */ KisNodeSP KRITAIMAGE_EXPORT recursiveFindNode(KisNodeSP node, std::function func); /** * Recursively searches for a node with specified Uuid */ KisNodeSP KRITAIMAGE_EXPORT findNodeByUuid(KisNodeSP root, const QUuid &uuid); }; #endif /* __KIS_LAYER_UTILS_H */ diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc index 0d066f1ce3..3e25cd31c8 100644 --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -1,464 +1,482 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (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_mask.h" #include // to prevent incomplete class types on "delete selection->flatten();" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "kis_painter.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_cached_paint_device.h" #include "kis_mask_projection_plane.h" #include "kis_raster_keyframe_channel.h" struct Q_DECL_HIDDEN KisMask::Private { Private(KisMask *_q) : q(_q), projectionPlane(new KisMaskProjectionPlane(q)) { } mutable KisSelectionSP selection; KisCachedPaintDevice paintDeviceCache; KisMask *q; /** * Due to the design of the Kra format the X,Y offset of the paint * device belongs to the node, but not to the device itself. So * the offset is set when the node is created, but not when the * selection is initialized. This causes the X,Y values to be * lost, since the selection doen not exist at the moment. That is * why we save it separately. */ QScopedPointer deferredSelectionOffset; KisAbstractProjectionPlaneSP projectionPlane; KisCachedSelection cachedSelection; void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice); }; KisMask::KisMask(const QString & name) : KisNode() , m_d(new Private(this)) { setName(name); } KisMask::KisMask(const KisMask& rhs) : KisNode(rhs) , KisIndirectPaintingSupport() , m_d(new Private(this)) { setName(rhs.name()); if (rhs.m_d->selection) { m_d->selection = new KisSelection(*rhs.m_d->selection.data()); m_d->selection->setParentNode(this); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } } } KisMask::~KisMask() { delete m_d; } void KisMask::setImage(KisImageWSP image) { KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0; KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image); if (m_d->selection) { m_d->selection->setDefaultBounds(defaultBounds); } } bool KisMask::allowAsChild(KisNodeSP node) const { Q_UNUSED(node); return false; } const KoColorSpace * KisMask::colorSpace() const { KisNodeSP parentNode = parent(); return parentNode ? parentNode->colorSpace() : 0; } const KoCompositeOp * KisMask::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisLayer. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ const KoColorSpace *colorSpace = this->colorSpace(); if (!colorSpace) return 0; const KoCompositeOp* op = colorSpace->compositeOp(compositeOpId()); return op ? op : colorSpace->compositeOp(COMPOSITE_OVER); } void KisMask::initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer) { m_d->initSelectionImpl(copyFrom, parentLayer, 0); } void KisMask::initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, copyFromDevice); } void KisMask::initSelection(KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, 0); } void KisMask::Private::initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice) { Q_ASSERT(parentLayer); KisPaintDeviceSP parentPaintDevice = parentLayer->original(); if (copyFrom) { /** * We can't use setSelection as we may not have parent() yet */ selection = new KisSelection(*copyFrom); selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); if (copyFrom->hasShapeSelection()) { delete selection->flatten(); } } else if (copyFromDevice) { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); QRect rc(copyFromDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), copyFromDevice, selection->pixelSelection(), rc); selection->pixelSelection()->invalidateOutlineCache(); } else { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, selection->pixelSelection()->colorSpace())); if (deferredSelectionOffset) { selection->setX(deferredSelectionOffset->x()); selection->setY(deferredSelectionOffset->y()); deferredSelectionOffset.reset(); } } selection->setParentNode(q); selection->updateProjection(); } KisSelectionSP KisMask::selection() const { return m_d->selection; } KisPaintDeviceSP KisMask::paintDevice() const { return selection()->pixelSelection(); } KisPaintDeviceSP KisMask::original() const { return paintDevice(); } KisPaintDeviceSP KisMask::projection() const { return paintDevice(); } KisAbstractProjectionPlaneSP KisMask::projectionPlane() const { return m_d->projectionPlane; } void KisMask::setSelection(KisSelectionSP selection) { m_d->selection = selection; if (parent()) { const KisLayer *parentLayer = qobject_cast(parent()); m_d->selection->setDefaultBounds(new KisDefaultBounds(parentLayer->image())); } m_d->selection->setParentNode(this); } void KisMask::select(const QRect & rc, quint8 selectedness) { KisSelectionSP sel = selection(); KisPixelSelectionSP psel = sel->pixelSelection(); psel->select(rc, selectedness); sel->updateProjection(rc); } QRect KisMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const { Q_UNUSED(src); Q_UNUSED(dst); Q_UNUSED(maskPos); Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors"); return rc; } +bool KisMask::paintsOutsideSelection() const +{ + return false; +} + void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const { if (selection()) { m_d->selection->updateProjection(applyRect); KisSelectionSP effectiveSelection = m_d->selection; QRect effectiveExtent; { // Access temporary target under the lock held KisIndirectPaintingSupport::ReadLocker l(this); - // extent of m_d->selection should also be accessed under a lock, - // because it might be being merged in by the temporary target atm - effectiveExtent = effectiveSelection->selectedRect(); + if (!paintsOutsideSelection()) { + // extent of m_d->selection should also be accessed under a lock, + // because it might be being merged in by the temporary target atm + effectiveExtent = effectiveSelection->selectedRect(); - if (hasTemporaryTarget()) { - effectiveExtent |= temporaryTarget()->extent(); - } + if (hasTemporaryTarget()) { + effectiveExtent |= temporaryTarget()->extent(); + } - if(!effectiveExtent.intersects(applyRect)) { - return; + if(!effectiveExtent.intersects(applyRect)) { + return; + } } if (hasTemporaryTarget()) { effectiveSelection = m_d->cachedSelection.getSelection(); effectiveSelection->setDefaultBounds(m_d->selection->pixelSelection()->defaultBounds()); KisPainter::copyAreaOptimized(applyRect.topLeft(), m_d->selection->pixelSelection(), effectiveSelection->pixelSelection(), applyRect); KisPainter gc(effectiveSelection->pixelSelection()); setupTemporaryPainter(&gc); gc.bitBlt(applyRect.topLeft(), temporaryTarget(), applyRect); } } - KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); - - QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos); - - // masks don't have any compositioning - KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection); - m_d->paintDeviceCache.putDevice(cacheDevice); + mergeInMaskInternal(projection, effectiveSelection, applyRect, needRect, maskPos); if (effectiveSelection != m_d->selection) { m_d->cachedSelection.putSelection(effectiveSelection); } } else { - KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); + mergeInMaskInternal(projection, 0, applyRect, needRect, maskPos); + } +} - cacheDevice->makeCloneFromRough(projection, needRect); - projection->clear(needRect); +void KisMask::mergeInMaskInternal(KisPaintDeviceSP projection, + KisSelectionSP effectiveSelection, + const QRect &applyRect, + const QRect &preparedNeedRect, + KisNode::PositionToFilthy maskPos) const +{ + KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); - decorateRect(cacheDevice, projection, applyRect, maskPos); + if (effectiveSelection) { + QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos); - m_d->paintDeviceCache.putDevice(cacheDevice); + // masks don't have any compositioning + KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection); + + } else { + cacheDevice->makeCloneFromRough(projection, preparedNeedRect); + projection->clear(preparedNeedRect); + + decorateRect(cacheDevice, projection, applyRect, maskPos); } + + m_d->paintDeviceCache.putDevice(cacheDevice); } QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); QRect resultRect = rect; if (m_d->selection) { QRect selectionExtent = m_d->selection->selectedRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { selectionExtent |= temporaryTarget->extent(); } resultRect &= selectionExtent; } return resultRect; } QRect KisMask::changeRect(const QRect &rect, PositionToFilthy pos) const { return KisMask::needRect(rect, pos); } QRect KisMask::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 if (KisNodeSP parent = this->parent()) { resultRect = parent->extent(); } return resultRect; } QRect KisMask::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 if (KisNodeSP parent = this->parent()) { resultRect = parent->exactBounds(); } return resultRect; } qint32 KisMask::x() const { return m_d->selection ? m_d->selection->x() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->x() : parent() ? parent()->x() : 0; } qint32 KisMask::y() const { return m_d->selection ? m_d->selection->y() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->y() : parent() ? parent()->y() : 0; } void KisMask::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(x, 0)); } else { m_d->deferredSelectionOffset->rx() = x; } } void KisMask::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(0, y)); } else { m_d->deferredSelectionOffset->ry() = y; } } QRect KisMask::nonDependentExtent() const { return QRect(); } QImage KisMask::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = selection() ? selection()->projection() : 0; return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } void KisMask::testingInitSelection(const QRect &rect, KisLayerSP parentLayer) { if (parentLayer) { m_d->selection = new KisSelection(new KisSelectionDefaultBounds(parentLayer->paintDevice(), parentLayer->image())); } else { m_d->selection = new KisSelection(); } m_d->selection->pixelSelection()->select(rect, OPACITY_OPAQUE_U8); m_d->selection->updateProjection(rect); m_d->selection->setParentNode(this); } KisKeyframeChannel *KisMask::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisPaintDeviceSP device = paintDevice(); if (device) { KisRasterKeyframeChannel *contentChannel = device->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } } return KisNode::requestKeyframeChannel(id); } void KisMask::baseNodeChangedCallback() { KisNodeSP up = parent(); KisLayer *layer = dynamic_cast(up.data()); if (layer) { - layer->notifyChildMaskChanged(this); + layer->notifyChildMaskChanged(); } KisNode::baseNodeChangedCallback(); } diff --git a/libs/image/kis_mask.h b/libs/image/kis_mask.h index 3f5fe1ef08..f7724a5a54 100644 --- a/libs/image/kis_mask.h +++ b/libs/image/kis_mask.h @@ -1,221 +1,230 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (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.b */ #ifndef _KIS_MASK_ #define _KIS_MASK_ #include #include "kis_types.h" #include "kis_global.h" #include "kis_node.h" #include "kis_indirect_painting_support.h" #include /** KisMask is the base class for all single channel mask-like paint devices in Krita. Masks can be rendered in different ways at different moments during the rendering stack. Masks are "owned" by layers (of any type), and cannot occur by themselves on themselves. The properties that masks implement are made available through the iterators created on their parent layer, or through iterators that can be created on the paint device that holds the mask data: masks are just paint devices, too. Masks should show up in the layerbox as sub-layers for the layer they are associated with and be ccp'able and draggable to other layers. Examples of masks are: - filter masks: like the alpha filter mask that is the most common type of mask and is simply known as "mask" in the gui. Other filter masks use any of krita's filters to filter the pixels of their parent. (In this they differ from adjustment layers, which filter all layers under them in their group stack). - selections: the selection mask is rendered after composition and zooming and determines the selectedness of the pixels of the parent layer. - painterly overlays: painterly overlays indicate a particular property of the pixel in the parent paint device they are associated with, like wetness, height or gravity. XXX: For now, all masks are 8 bit. Make the channel depth settable. */ class KRITAIMAGE_EXPORT KisMask : public KisNode, public KisIndirectPaintingSupport { Q_OBJECT public: /** * Create a new KisMask. */ KisMask(const QString & name); /** * Copy the mask */ KisMask(const KisMask& rhs); ~KisMask() override; void setImage(KisImageWSP image) override; bool allowAsChild(KisNodeSP node) const override; /** * @brief initSelection initializes the selection for the mask from * the given selection's projection. * @param copyFrom the selection we base the mask on * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer); /** * @brief initSelection initializes the selection for the mask from * the given paint device. * @param copyFromDevice the paint device we base the mask on * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer); /** * @brief initSelection initializes an empty selection * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisLayerSP parentLayer); const KoColorSpace * colorSpace() const override; const KoCompositeOp * compositeOp() const override; /** * Return the selection associated with this mask. A selection can * contain both a paint device and shapes. */ KisSelectionSP selection() const; /** * @return the selection: if you paint on mask, you paint on the selections */ KisPaintDeviceSP paintDevice() const override; /** * @return the same as paintDevice() */ KisPaintDeviceSP original() const override; /** * @return the same as paintDevice() */ KisPaintDeviceSP projection() const override; KisAbstractProjectionPlaneSP projectionPlane() const override; /** * Change the selection to the specified selection object. The * selection is deep copied. */ void setSelection(KisSelectionSP selection); /** * Selected the specified rect with the specified amount of selectedness. */ void select(const QRect & rc, quint8 selectedness = MAX_SELECTED); /** * The extent and bounds of the mask are those of the selection inside */ QRect extent() const override; QRect exactBounds() const override; /** * overridden from KisBaseNode */ qint32 x() const override; /** * overridden from KisBaseNode */ void setX(qint32 x) override; /** * overridden from KisBaseNode */ qint32 y() const override; /** * overridden from KisBaseNode */ void setY(qint32 y) override; /** * Usually masks themselves do not have any paint device and * all their final effect on the layer stack is computed using * the changeRect() of the dirty rect of the parent layer. Their * extent() and exectBounds() methods work the same way: by taking * the extent of the parent layer and computing the rect basing * on it. But some of the masks like Colorize Mask may have their * own "projection", which is painted independently from the changed * area of the parent layer. This additional "non-dependent" extent * is added to the extent of the parent layer. */ virtual QRect nonDependentExtent() const; QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QImage createThumbnail(qint32 w, qint32 h) override; void testingInitSelection(const QRect &rect, KisLayerSP parentLayer); protected: /** * Apply the effect the projection using the mask as a selection. * Made public in KisEffectMask */ void apply(KisPaintDeviceSP projection, const QRect & applyRect, const QRect & needRect, PositionToFilthy maskPos) const; + + virtual void mergeInMaskInternal(KisPaintDeviceSP projection, + KisSelectionSP effectiveSelection, + const QRect &applyRect, const QRect &preparedNeedRect, + PositionToFilthy maskPos) const; + + virtual QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const; + virtual bool paintsOutsideSelection() const; + KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; void baseNodeChangedCallback() override; private: friend class KisMaskProjectionPlane; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/kis_node_graph_listener.cpp b/libs/image/kis_node_graph_listener.cpp index 6e9bcf2029..583160c6a6 100644 --- a/libs/image/kis_node_graph_listener.cpp +++ b/libs/image/kis_node_graph_listener.cpp @@ -1,102 +1,107 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_graph_listener.h" #include "kis_time_range.h" #include #include struct Q_DECL_HIDDEN KisNodeGraphListener::Private { Private() : sequenceNumber(0) {} int sequenceNumber; }; KisNodeGraphListener::KisNodeGraphListener() : m_d(new Private()) { } KisNodeGraphListener::~KisNodeGraphListener() { } void KisNodeGraphListener::aboutToAddANode(KisNode */*parent*/, int /*index*/) { m_d->sequenceNumber++; } void KisNodeGraphListener::nodeHasBeenAdded(KisNode */*parent*/, int /*index*/) { m_d->sequenceNumber++; } void KisNodeGraphListener::aboutToRemoveANode(KisNode */*parent*/, int /*index*/) { m_d->sequenceNumber++; } void KisNodeGraphListener::nodeHasBeenRemoved(KisNode */*parent*/, int /*index*/) { m_d->sequenceNumber++; } void KisNodeGraphListener::aboutToMoveNode(KisNode * /*node*/, int /*oldIndex*/, int /*newIndex*/) { m_d->sequenceNumber++; } void KisNodeGraphListener::nodeHasBeenMoved(KisNode * /*node*/, int /*oldIndex*/, int /*newIndex*/) { m_d->sequenceNumber++; } int KisNodeGraphListener::graphSequenceNumber() const { return m_d->sequenceNumber; } void KisNodeGraphListener::nodeChanged(KisNode * /*node*/) { } void KisNodeGraphListener::invalidateAllFrames() { } void KisNodeGraphListener::notifySelectionChanged() { } void KisNodeGraphListener::requestProjectionUpdate(KisNode * /*node*/, const QVector &/*rects*/, bool /*resetAnimationCache*/) { } void KisNodeGraphListener::invalidateFrames(const KisTimeRange &range, const QRect &rect) { Q_UNUSED(range); Q_UNUSED(rect); } void KisNodeGraphListener::requestTimeSwitch(int time) { Q_UNUSED(time); } + +KisNode *KisNodeGraphListener::graphOverlayNode() const +{ + return 0; +} diff --git a/libs/image/kis_node_graph_listener.h b/libs/image/kis_node_graph_listener.h index f0290f5856..83951b1062 100644 --- a/libs/image/kis_node_graph_listener.h +++ b/libs/image/kis_node_graph_listener.h @@ -1,123 +1,125 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_NODE_GRAPH_LISTENER_H_ #define KIS_NODE_GRAPH_LISTENER_H_ #include "kritaimage_export.h" #include class KisTimeRange; class KisNode; class QRect; /** * Implementations of this class are called by nodes whenever the node * graph changes. These implementations can then emit the right * signals so Qt interview models can be updated before and after * changes. * * The reason for this go-between is that we don't want our nodes to * be QObjects, nor to have sig-slot connections between every node * and every mode. * * It also manages the sequence number of the graph. This is a number * which can be used as a checksum for whether the graph has chenged * from some period of time or not. \see graphSequenceNumber() */ class KRITAIMAGE_EXPORT KisNodeGraphListener { public: KisNodeGraphListener(); virtual ~KisNodeGraphListener(); /** * Inform the model that we're going to add a node. */ virtual void aboutToAddANode(KisNode *parent, int index); /** * Inform the model we're done adding a node. */ virtual void nodeHasBeenAdded(KisNode *parent, int index); /** * Inform the model we're going to remove a node. */ virtual void aboutToRemoveANode(KisNode *parent, int index); /** * Inform the model we're done removing a node. */ virtual void nodeHasBeenRemoved(KisNode *parent, int index); /** * Inform the model we're about to start moving a node (which * includes removing and adding the same node) */ virtual void aboutToMoveNode(KisNode * node, int oldIndex, int newIndex); /** * Inform the model we're done moving the node: it has been * removed and added successfully */ virtual void nodeHasBeenMoved(KisNode * node, int oldIndex, int newIndex); virtual void nodeChanged(KisNode * node); virtual void invalidateAllFrames(); /** * Inform the model that one of the selections in the graph is * changed. The sender is not passed to the function (at least for * now) because the UI should decide itself whether it needs to * fetch new selection of not. */ virtual void notifySelectionChanged(); /** * Inform the model that a node has been changed (setDirty) */ virtual void requestProjectionUpdate(KisNode * node, const QVector &rects, bool resetAnimationCache); virtual void invalidateFrames(const KisTimeRange &range, const QRect &rect); virtual void requestTimeSwitch(int time); + virtual KisNode* graphOverlayNode() const; + /** * Returns the sequence of the graph. * * Every time some operation performed, which might change the * hierarchy of the nodes, the sequence number grows by one. So * if you have any information about the graph which was acquired * when the sequence number was X and now it has become Y, it * means your information is outdated. * * It is used in the scheduler for checking whether queued walkers * should be regenerated. */ int graphSequenceNumber() const; private: struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/image/kis_projection_leaf.cpp b/libs/image/kis_projection_leaf.cpp index 09d20668e3..6fda2dd6ee 100644 --- a/libs/image/kis_projection_leaf.cpp +++ b/libs/image/kis_projection_leaf.cpp @@ -1,281 +1,368 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_projection_leaf.h" #include #include "kis_layer.h" #include "kis_mask.h" #include "kis_group_layer.h" +#include "kis_selection_mask.h" #include "kis_adjustment_layer.h" #include "krita_utils.h" #include "kis_refresh_subtree_walker.h" #include "kis_async_merger.h" +#include "kis_node_graph_listener.h" struct Q_DECL_HIDDEN KisProjectionLeaf::Private { Private(KisNode *_node) : node(_node) {} KisNode* node; static bool checkPassThrough(const KisNode *node) { const KisGroupLayer *group = qobject_cast(node); return group && group->passThroughMode(); } + static bool isSelectionMask(const KisNode *node) { + return qobject_cast(node); + } + + static KisNodeSP skipSelectionMasksForward(KisNodeSP node) { + while (node && isSelectionMask(node)) { + node = node->nextSibling(); + } + return node; + } + + static KisNodeSP skipSelectionMasksBackward(KisNodeSP node) { + while (node && isSelectionMask(node)) { + node = node->prevSibling(); + } + return node; + } + bool checkParentPassThrough() { return node->parent() && checkPassThrough(node->parent()); } bool checkThisPassThrough() { return checkPassThrough(node); } + KisProjectionLeafSP overlayProjectionLeaf() const { + return node && node->graphListener() && node->graphListener()->graphOverlayNode() ? + node->graphListener()->graphOverlayNode()->projectionLeaf() : 0; + } + + bool isTopmostNode() const { + return !skipSelectionMasksForward(node->nextSibling()) && + node->parent() && + !node->parent()->parent(); + } + + KisNodeSP findRoot() const { + KisNodeSP root = node; + + while (root->parent()) { + root = root->parent(); + } + + return root; + } + void temporarySetPassThrough(bool value) { KisGroupLayer *group = qobject_cast(node); if (!group) return; group->setPassThroughMode(value); } }; KisProjectionLeaf::KisProjectionLeaf(KisNode *node) : m_d(new Private(node)) { } KisProjectionLeaf::~KisProjectionLeaf() { } KisProjectionLeafSP KisProjectionLeaf::parent() const { - KisNodeSP node = m_d->node->parent(); + KisNodeSP node; + + if (Private::isSelectionMask(m_d->node)) { + if (m_d->overlayProjectionLeaf() == this) { + node = m_d->findRoot(); + } + } else { + node = m_d->node->parent(); + } while (node && Private::checkPassThrough(node)) { node = node->parent(); } return node ? node->projectionLeaf() : KisProjectionLeafSP(); } KisProjectionLeafSP KisProjectionLeaf::firstChild() const { KisNodeSP node; if (!m_d->checkThisPassThrough()) { node = m_d->node->firstChild(); + node = Private::skipSelectionMasksForward(node); + } + + if (!node && isRoot()) { + KisProjectionLeafSP overlayLeaf = m_d->overlayProjectionLeaf(); + if (overlayLeaf) { + return overlayLeaf; + } } return node ? node->projectionLeaf() : KisProjectionLeafSP(); } KisProjectionLeafSP KisProjectionLeaf::lastChild() const { KisNodeSP node; + if (isRoot()) { + KisProjectionLeafSP overlayLeaf = m_d->overlayProjectionLeaf(); + if (overlayLeaf) { + return overlayLeaf; + } + } + if (!m_d->checkThisPassThrough()) { node = m_d->node->lastChild(); + node = Private::skipSelectionMasksBackward(node); } return node ? node->projectionLeaf() : KisProjectionLeafSP(); } KisProjectionLeafSP KisProjectionLeaf::prevSibling() const { + if (Private::isSelectionMask(m_d->node)) { + KisProjectionLeafSP leaf; + + if (m_d->overlayProjectionLeaf() == this) { + KisNodeSP node = m_d->findRoot()->lastChild(); + node = Private::skipSelectionMasksBackward(node); + leaf = node->projectionLeaf(); + } + + return leaf; + } + KisNodeSP node; if (m_d->checkThisPassThrough()) { node = m_d->node->lastChild(); + node = Private::skipSelectionMasksBackward(node); } if (!node) { node = m_d->node->prevSibling(); + node = Private::skipSelectionMasksBackward(node); } const KisProjectionLeaf *leaf = this; while (!node && leaf->m_d->checkParentPassThrough()) { leaf = leaf->node()->parent()->projectionLeaf().data(); node = leaf->node()->prevSibling(); + node = Private::skipSelectionMasksBackward(node); } return node ? node->projectionLeaf() : KisProjectionLeafSP(); } KisProjectionLeafSP KisProjectionLeaf::nextSibling() const { + if (Private::isSelectionMask(m_d->node)) { + return KisProjectionLeafSP(); + } + + KisProjectionLeafSP overlayLeaf = m_d->overlayProjectionLeaf(); + if (overlayLeaf && m_d->isTopmostNode()) { + return overlayLeaf; + } + KisNodeSP node = m_d->node->nextSibling(); + node = Private::skipSelectionMasksForward(node); while (node && Private::checkPassThrough(node) && node->firstChild()) { node = node->firstChild(); + node = Private::skipSelectionMasksForward(node); } if (!node && m_d->checkParentPassThrough()) { node = m_d->node->parent(); + node = Private::skipSelectionMasksForward(node); } return node ? node->projectionLeaf() : KisProjectionLeafSP(); } -bool KisProjectionLeaf::hasChildren() const -{ - return m_d->node->firstChild(); -} - KisNodeSP KisProjectionLeaf::node() const { return m_d->node; } KisAbstractProjectionPlaneSP KisProjectionLeaf::projectionPlane() const { return m_d->node->projectionPlane(); } bool KisProjectionLeaf::accept(KisNodeVisitor &visitor) { return m_d->node->accept(visitor); } KisPaintDeviceSP KisProjectionLeaf::original() { return m_d->node->original(); } KisPaintDeviceSP KisProjectionLeaf::projection() { return m_d->node->projection(); } bool KisProjectionLeaf::isRoot() const { return (bool)!m_d->node->parent(); } bool KisProjectionLeaf::isLayer() const { return (bool)qobject_cast(m_d->node); } bool KisProjectionLeaf::isMask() const { return (bool)qobject_cast(m_d->node); } bool KisProjectionLeaf::canHaveChildLayers() const { return (bool)qobject_cast(m_d->node); } bool KisProjectionLeaf::dependsOnLowerNodes() const { return (bool)qobject_cast(m_d->node); } bool KisProjectionLeaf::visible() const { // TODO: check opacity as well! bool hiddenByParentPassThrough = false; KisNodeSP node = m_d->node->parent(); while (node && node->projectionLeaf()->m_d->checkThisPassThrough()) { hiddenByParentPassThrough |= !node->visible(); node = node->parent(); } return m_d->node->visible(false) && !m_d->checkThisPassThrough() && !hiddenByParentPassThrough; } quint8 KisProjectionLeaf::opacity() const { quint8 resultOpacity = m_d->node->opacity(); if (m_d->checkParentPassThrough()) { quint8 parentOpacity = m_d->node->parent()->projectionLeaf()->opacity(); resultOpacity = KritaUtils::mergeOpacity(resultOpacity, parentOpacity); } return resultOpacity; } QBitArray KisProjectionLeaf::channelFlags() const { QBitArray channelFlags; KisLayer *layer = qobject_cast(m_d->node); if (!layer) return channelFlags; channelFlags = layer->channelFlags(); if (m_d->checkParentPassThrough()) { QBitArray parentChannelFlags; if (*m_d->node->colorSpace() == *m_d->node->parent()->colorSpace()) { KisLayer *parentLayer = qobject_cast(m_d->node->parent().data()); parentChannelFlags = parentLayer->channelFlags(); } channelFlags = KritaUtils::mergeChannelFlags(channelFlags, parentChannelFlags); } return channelFlags; } bool KisProjectionLeaf::isStillInGraph() const { return (bool)m_d->node->graphListener(); } bool KisProjectionLeaf::isDroppedMask() const { return qobject_cast(m_d->node) && m_d->checkParentPassThrough(); } /** * This method is rather slow and dangerous. It should be executes in * exclusive environment only. */ void KisProjectionLeaf::explicitlyRegeneratePassThroughProjection() { if (!m_d->checkThisPassThrough()) return; m_d->temporarySetPassThrough(false); const QRect updateRect = projection()->defaultBounds()->bounds(); KisRefreshSubtreeWalker walker(updateRect); walker.collectRects(m_d->node, updateRect); KisAsyncMerger merger; merger.startMerge(walker); m_d->temporarySetPassThrough(true); } diff --git a/libs/image/kis_projection_leaf.h b/libs/image/kis_projection_leaf.h index a9a4eba407..c73a77c1f3 100644 --- a/libs/image/kis_projection_leaf.h +++ b/libs/image/kis_projection_leaf.h @@ -1,79 +1,77 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PROJECTION_LEAF_H #define __KIS_PROJECTION_LEAF_H #include #include "kis_types.h" #include "kritaimage_export.h" class KisNodeVisitor; class KRITAIMAGE_EXPORT KisProjectionLeaf { public: KisProjectionLeaf(KisNode *node); virtual ~KisProjectionLeaf(); KisProjectionLeafSP parent() const; KisProjectionLeafSP firstChild() const; KisProjectionLeafSP lastChild() const; KisProjectionLeafSP prevSibling() const; KisProjectionLeafSP nextSibling() const; - bool hasChildren() const; - KisNodeSP node() const; KisAbstractProjectionPlaneSP projectionPlane() const; bool accept(KisNodeVisitor &visitor); KisPaintDeviceSP original(); KisPaintDeviceSP projection(); bool isRoot() const; bool isLayer() const; bool isMask() const; bool canHaveChildLayers() const; bool dependsOnLowerNodes() const; bool visible() const; quint8 opacity() const; QBitArray channelFlags() const; bool isStillInGraph() const; bool isDroppedMask() const; /** * Regenerate projection of the current group layer iff it is * pass-through mode. * * WARNING: must be called either under the image lock held * or in the context of an exclusive stroke job. */ void explicitlyRegeneratePassThroughProjection(); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_PROJECTION_LEAF_H */ diff --git a/libs/image/kis_refresh_subtree_walker.h b/libs/image/kis_refresh_subtree_walker.h index 4965f55019..9209e029c2 100644 --- a/libs/image/kis_refresh_subtree_walker.h +++ b/libs/image/kis_refresh_subtree_walker.h @@ -1,128 +1,135 @@ /* * 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. */ #ifndef __KIS_REFRESH_SUBTREE_WALKER_H #define __KIS_REFRESH_SUBTREE_WALKER_H #include "kis_types.h" #include "kis_base_rects_walker.h" class KRITAIMAGE_EXPORT KisRefreshSubtreeWalker : public virtual KisBaseRectsWalker { public: KisRefreshSubtreeWalker(QRect cropRect) { setCropRect(cropRect); } UpdateType type() const override { return UNSUPPORTED; } ~KisRefreshSubtreeWalker() override { } protected: KisRefreshSubtreeWalker() {} QRect calculateChangeRect(KisProjectionLeafSP startWith, const QRect &requestedRect) { if(!startWith->isLayer()) return requestedRect; QRect childrenRect; QRect tempRect = requestedRect; bool changeRectVaries = false; KisProjectionLeafSP currentLeaf = startWith->firstChild(); KisProjectionLeafSP prevLeaf; KisProjectionLeafSP nextLeaf; while(currentLeaf) { nextLeaf = currentLeaf->nextSibling(); if(currentLeaf->isLayer()) { tempRect |= calculateChangeRect(currentLeaf, requestedRect); if(!changeRectVaries) changeRectVaries = tempRect != requestedRect; childrenRect = tempRect; prevLeaf = currentLeaf; } currentLeaf = nextLeaf; } tempRect |= startWith->projectionPlane()->changeRect(requestedRect | childrenRect); if(!changeRectVaries) changeRectVaries = tempRect != requestedRect; setExplicitChangeRect(startWith, tempRect, changeRectVaries); return tempRect; } void startTrip(KisProjectionLeafSP startWith) override { setExplicitChangeRect(startWith, requestedRect(), false); if (isStartLeaf(startWith)) { KisProjectionLeafSP extraUpdateLeaf = startWith; if (startWith->isMask()) { /** * When the mask is the root of the update, update * its parent projection using N_EXTRA method. * * This special update is necessary because the following * wolker will work in N_ABOVE_FILTHY mode only */ extraUpdateLeaf = startWith->parent(); } - NodePosition pos = N_EXTRA | calculateNodePosition(extraUpdateLeaf); - registerNeedRect(extraUpdateLeaf, pos); + /** + * Sometimes it may happen that the mask is placed outside layers hierarchy + * (e.g. inactive selection mask), then the projection leafs will not point + * to anywhere + */ + if (extraUpdateLeaf) { + NodePosition pos = N_EXTRA | calculateNodePosition(extraUpdateLeaf); + registerNeedRect(extraUpdateLeaf, pos); + } } KisProjectionLeafSP currentLeaf = startWith->lastChild(); while(currentLeaf) { NodePosition pos = N_FILTHY | calculateNodePosition(currentLeaf); registerNeedRect(currentLeaf, pos); currentLeaf = currentLeaf->prevSibling(); } currentLeaf = startWith->lastChild(); while(currentLeaf) { if(currentLeaf->canHaveChildLayers()) { startTrip(currentLeaf); } currentLeaf = currentLeaf->prevSibling(); } } }; #endif /* __KIS_REFRESH_SUBTREE_WALKER_H */ diff --git a/libs/image/kis_selection_mask.cpp b/libs/image/kis_selection_mask.cpp index a5cb4f8614..824f9a9089 100644 --- a/libs/image/kis_selection_mask.cpp +++ b/libs/image/kis_selection_mask.cpp @@ -1,183 +1,308 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_mask.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_selection.h" #include #include #include #include "kis_fill_painter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_pixel_selection.h" #include "kis_undo_adapter.h" #include #include #include "kis_thread_safe_signal_compressor.h" #include "kis_layer_properties_icons.h" +#include "kis_cached_paint_device.h" + +#include "kis_image_config.h" +#include "KisImageConfigNotifier.h" struct Q_DECL_HIDDEN KisSelectionMask::Private { public: Private(KisSelectionMask *_q) : q(_q) , updatesCompressor(0) + , maskColor(Qt::green, KoColorSpaceRegistry::instance()->rgb8()) {} KisSelectionMask *q; KisImageWSP image; + KisCachedPaintDevice paintDeviceCache; + KisCachedSelection cachedSelection; KisThreadSafeSignalCompressor *updatesCompressor; + KoColor maskColor; void slotSelectionChangedCompressed(); + void slotConfigChanged(); }; KisSelectionMask::KisSelectionMask(KisImageWSP image) - : KisMask("selection") + : KisEffectMask() , m_d(new Private(this)) { + setName("selection"); setActive(false); m_d->image = image; m_d->updatesCompressor = new KisThreadSafeSignalCompressor(300, KisSignalCompressor::POSTPONE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(image->thread()); + + connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); + m_d->slotConfigChanged(); } KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs) - : KisMask(rhs) + : KisEffectMask(rhs) , m_d(new Private(this)) { m_d->image = rhs.image(); m_d->updatesCompressor = new KisThreadSafeSignalCompressor(300, KisSignalCompressor::POSTPONE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(m_d->image->thread()); + + connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); + m_d->slotConfigChanged(); } KisSelectionMask::~KisSelectionMask() { m_d->updatesCompressor->deleteLater(); delete m_d; } QIcon KisSelectionMask::icon() const { return KisIconUtils::loadIcon("selectionMask"); } +void KisSelectionMask::mergeInMaskInternal(KisPaintDeviceSP projection, + KisSelectionSP effectiveSelection, + const QRect &applyRect, + const QRect &preparedNeedRect, + KisNode::PositionToFilthy maskPos) const +{ + Q_UNUSED(maskPos); + Q_UNUSED(preparedNeedRect); + if (!effectiveSelection) return; + + { + KisSelectionSP mainMaskSelection = this->selection(); + if (mainMaskSelection && + (!mainMaskSelection->isVisible() || + mainMaskSelection->pixelSelection()->defaultBounds()->externalFrameActive())) { + + return; + } + } + + KisPaintDeviceSP fillDevice = m_d->paintDeviceCache.getDevice(projection); + fillDevice->setDefaultPixel(m_d->maskColor); + + const QRect selectionExtent = effectiveSelection->selectedRect(); + + if (selectionExtent.contains(applyRect) || selectionExtent.intersects(applyRect)) { + KisSelectionSP invertedSelection = m_d->cachedSelection.getSelection(); + + invertedSelection->pixelSelection()->makeCloneFromRough(effectiveSelection->pixelSelection(), applyRect); + invertedSelection->pixelSelection()->invert(); + + KisPainter gc(projection); + gc.setSelection(invertedSelection); + gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); + + m_d->cachedSelection.putSelection(invertedSelection); + + } else { + KisPainter gc(projection); + gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); + } + + m_d->paintDeviceCache.putDevice(fillDevice); +} + +bool KisSelectionMask::paintsOutsideSelection() const +{ + return true; +} + void KisSelectionMask::setSelection(KisSelectionSP selection) { if (selection) { - KisMask::setSelection(selection); + KisEffectMask::setSelection(selection); } else { - KisMask::setSelection(new KisSelection()); + KisEffectMask::setSelection(new KisSelection()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->alpha8(); KisFillPainter gc(KisPaintDeviceSP(this->selection()->pixelSelection().data())); gc.fillRect(image()->bounds(), KoColor(Qt::white, cs), MAX_SELECTED); gc.end(); } setDirty(); } KisImageWSP KisSelectionMask::image() const { return m_d->image; } bool KisSelectionMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisSelectionMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KisBaseNode::PropertyList KisSelectionMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::selectionActive, active()); return l; } void KisSelectionMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { - KisMask::setSectionModelProperties(properties); + KisEffectMask::setSectionModelProperties(properties); setActive(properties.at(2).state.toBool()); } void KisSelectionMask::setVisible(bool visible, bool isLoading) { + const bool oldVisible = this->visible(false); setNodeProperty("visible", visible); - if (!isLoading) { + if (!isLoading && visible != oldVisible) { if (selection()) selection()->setVisible(visible); emit(visibilityChanged(visible)); } } bool KisSelectionMask::active() const { return nodeProperties().boolProperty("active", true); } void KisSelectionMask::setActive(bool active) { KisImageWSP image = this->image(); KisLayerSP parentLayer = qobject_cast(parent().data()); if (active && parentLayer) { KisSelectionMaskSP activeMask = parentLayer->selectionMask(); - if (activeMask) { + if (activeMask && activeMask != this) { activeMask->setActive(false); } } + const bool oldActive = this->active(); setNodeProperty("active", active); - if (image) { + if (image && oldActive != active) { image->nodeChanged(this); image->undoAdapter()->emitSelectionChanged(); } } +QRect KisSelectionMask::needRect(const QRect &rect, KisNode::PositionToFilthy pos) const +{ + Q_UNUSED(pos); + + // selection masks just add an overlay, so the needed rect is simply passed through + return rect; +} + +QRect KisSelectionMask::changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const +{ + Q_UNUSED(pos); + + // selection masks just add an overlay, so the changed rect is simply passed through + return rect; +} + +QRect KisSelectionMask::extent() const +{ + // since mask overlay is inverted, the mask paints over + // the entire image bounds + + QRect resultRect; + + KisSelectionSP selection = this->selection(); + + if (selection) { + resultRect = selection->pixelSelection()->defaultBounds()->bounds(); + } else if (KisNodeSP parent = this->parent()) { + KisPaintDeviceSP dev = parent->projection(); + if (dev) { + resultRect = dev->defaultBounds()->bounds(); + } + } + + return resultRect; +} + +QRect KisSelectionMask::exactBounds() const +{ + return extent(); +} + void KisSelectionMask::notifySelectionChangedCompressed() { m_d->updatesCompressor->start(); } void KisSelectionMask::Private::slotSelectionChangedCompressed() { KisSelectionSP currentSelection = q->selection(); if (!currentSelection) return; currentSelection->notifySelectionChanged(); } +void KisSelectionMask::Private::slotConfigChanged() +{ + const KoColorSpace *cs = image ? + image->colorSpace() : + KoColorSpaceRegistry::instance()->rgb8(); + + KisImageConfig cfg(true); + + maskColor = KoColor(cfg.selectionOverlayMaskColor(), cs); + + if (image && image->overlaySelectionMask() == q) { + q->setDirty(); + } +} + #include "moc_kis_selection_mask.cpp" diff --git a/libs/image/kis_selection_mask.h b/libs/image/kis_selection_mask.h index 1ef431b48b..0c6be056b0 100644 --- a/libs/image/kis_selection_mask.h +++ b/libs/image/kis_selection_mask.h @@ -1,82 +1,96 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_SELECTION_MASK_ #define _KIS_SELECTION_MASK_ #include #include "kis_base_node.h" #include "kis_types.h" -#include "kis_mask.h" +#include "kis_effect_mask.h" /** * An selection mask is a single channel mask that applies a * particular selection to the layer the mask belongs to. A selection * can contain both vector and pixel selection components. */ -class KRITAIMAGE_EXPORT KisSelectionMask : public KisMask +class KRITAIMAGE_EXPORT KisSelectionMask : public KisEffectMask { Q_OBJECT public: /** * Create an empty selection mask. There is filter and no layer * associated with this mask. */ KisSelectionMask(KisImageWSP image); ~KisSelectionMask() override; KisSelectionMask(const KisSelectionMask& rhs); QIcon icon() const override; KisNodeSP clone() const override { return KisNodeSP(new KisSelectionMask(*this)); } + void mergeInMaskInternal(KisPaintDeviceSP projection, + KisSelectionSP effectiveSelection, + const QRect &applyRect, const QRect &preparedNeedRect, + KisNode::PositionToFilthy maskPos) const override; + + bool paintsOutsideSelection() const override; + /// Set the selection of this adjustment layer to a copy of selection. void setSelection(KisSelectionSP selection); bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; void setVisible(bool visible, bool isLoading = false) override; bool active() const; void setActive(bool active); + QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; + QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; + + QRect extent() const override; + QRect exactBounds() const override; + /** * This method works like the one in KisSelection, but it * compressed the incoming events instead of processing each of * them separately. */ void notifySelectionChangedCompressed(); private: Q_PRIVATE_SLOT(m_d, void slotSelectionChangedCompressed()); + Q_PRIVATE_SLOT(m_d, void slotConfigChanged()); KisImageWSP image() const; struct Private; Private * const m_d; }; #endif //_KIS_SELECTION_MASK_ diff --git a/libs/image/kis_time_range.cpp b/libs/image/kis_time_range.cpp index 363e970bc4..c166e4d48e 100644 --- a/libs/image/kis_time_range.cpp +++ b/libs/image/kis_time_range.cpp @@ -1,144 +1,151 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_time_range.h" #include #include "kis_keyframe_channel.h" #include "kis_node.h" +#include "kis_layer_utils.h" struct KisTimeRangeStaticRegistrar { KisTimeRangeStaticRegistrar() { qRegisterMetaType("KisTimeRange"); } }; static KisTimeRangeStaticRegistrar __registrar; QDebug operator<<(QDebug dbg, const KisTimeRange &r) { dbg.nospace() << "KisTimeRange(" << r.start() << ", " << r.end() << ")"; return dbg.space(); } -void KisTimeRange::calculateTimeRangeRecursive(const KisNode *node, int time, KisTimeRange &range, bool exclusive) +KisTimeRange KisTimeRange::calculateIdenticalFramesRecursive(const KisNode *node, int time) { - if (!node->visible()) return; + KisTimeRange range = KisTimeRange::infinite(0); - if (exclusive) { - // Intersection - range &= calculateNodeIdenticalFrames(node, time); - } else { - // Union - range |= calculateNodeAffectedFrames(node, time); - } + KisLayerUtils::recursiveApplyNodes(node, + [&range, time] (const KisNode *node) { + if (node->visible()) { + range &= calculateNodeIdenticalFrames(node, time); + } + }); - KisNodeSP child = node->firstChild(); - while (child) { - calculateTimeRangeRecursive(child, time, range, exclusive); - child = child->nextSibling(); - } + return range; +} + +KisTimeRange KisTimeRange::calculateAffectedFramesRecursive(const KisNode *node, int time) +{ + KisTimeRange range; + + KisLayerUtils::recursiveApplyNodes(node, + [&range, time] (const KisNode *node) { + if (node->visible()) { + range |= calculateNodeIdenticalFrames(node, time); + } + }); + + return range; } KisTimeRange KisTimeRange::calculateNodeIdenticalFrames(const KisNode *node, int time) { KisTimeRange range = KisTimeRange::infinite(0); const QMap channels = node->keyframeChannels(); - if (channels.isEmpty() || - !channels.contains(KisKeyframeChannel::Content.id())) { - - return range; - } - Q_FOREACH (const KisKeyframeChannel *channel, channels) { // Intersection range &= channel->identicalFrames(time); } return range; } KisTimeRange KisTimeRange::calculateNodeAffectedFrames(const KisNode *node, int time) { KisTimeRange range; if (!node->visible()) return range; const QMap channels = node->keyframeChannels(); + // TODO: channels should report to the image which channel exactly has changed + // to avoid the dirty range to be stretched into infinity! + if (channels.isEmpty() || !channels.contains(KisKeyframeChannel::Content.id())) { range = KisTimeRange::infinite(0); return range; } Q_FOREACH (const KisKeyframeChannel *channel, channels) { // Union range |= channel->affectedFrames(time); } return range; } namespace KisDomUtils { void saveValue(QDomElement *parent, const QString &tag, const KisTimeRange &range) { QDomDocument doc = parent->ownerDocument(); QDomElement e = doc.createElement(tag); parent->appendChild(e); e.setAttribute("type", "timerange"); if (range.isValid()) { e.setAttribute("from", toString(range.start())); if (!range.isInfinite()) { e.setAttribute("to", toString(range.end())); } } } bool loadValue(const QDomElement &parent, const QString &tag, KisTimeRange *range) { QDomElement e; if (!findOnlyElement(parent, tag, &e)) return false; if (!Private::checkType(e, "timerange")) return false; int start = toInt(e.attribute("from", "-1")); int end = toInt(e.attribute("to", "-1")); if (start == -1) { range = new KisTimeRange(); } else if (end == -1) { *range = KisTimeRange::infinite(start); } else { *range = KisTimeRange::fromTime(start, end); } return true; } } diff --git a/libs/image/kis_time_range.h b/libs/image/kis_time_range.h index a6af4eab50..ca103bed7e 100644 --- a/libs/image/kis_time_range.h +++ b/libs/image/kis_time_range.h @@ -1,152 +1,153 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TIME_RANGE_H #define __KIS_TIME_RANGE_H #include "kritaimage_export.h" #include #include #include #include #include "kis_types.h" #include class KRITAIMAGE_EXPORT KisTimeRange : public boost::equality_comparable { public: inline KisTimeRange() : m_start(0), m_end(-1) { } inline KisTimeRange(int start, int duration) : m_start(start), m_end(start + duration - 1) { } inline KisTimeRange(int start, int end, bool) : m_start(start), m_end(end) { } bool operator==(const KisTimeRange &rhs) const { return rhs.m_start == m_start && rhs.m_end == m_end; } KisTimeRange& operator|=(const KisTimeRange &rhs) { if (!isValid()) { m_start = rhs.start(); } else if (rhs.isValid()) { m_start = std::min(m_start, rhs.start()); } if (rhs.isInfinite() || isInfinite()) { m_end = std::numeric_limits::min(); } else if (!isValid()) { m_end = rhs.m_end; } else { m_end = std::max(m_end, rhs.m_end); } return *this; } KisTimeRange& operator&=(const KisTimeRange &rhs) { if (!isValid()) { return *this; } else if (!rhs.isValid()) { m_start = rhs.start(); m_end = rhs.m_end; return *this; } else { m_start = std::max(m_start, rhs.start()); } if (isInfinite()) { m_end = rhs.m_end; } else if (!rhs.isInfinite()) { m_end = std::min(m_end, rhs.m_end); } return *this; } inline int start() const { return m_start; } inline int end() const { return m_end; } inline int duration() const { return m_end >= m_start ? m_end - m_start + 1 : 0; } inline bool isInfinite() const { return m_end == std::numeric_limits::min(); } inline bool isValid() const { return (m_end >= m_start) || (m_end == std::numeric_limits::min() && m_start >= 0); } inline bool contains(int time) const { if (m_end == std::numeric_limits::min()) { return m_start <= time; } return m_start <= time && time <= m_end; } static inline KisTimeRange fromTime(int start, int end) { return KisTimeRange(start, end, true); } static inline KisTimeRange infinite(int start) { return KisTimeRange(start, std::numeric_limits::min(), true); } - static void calculateTimeRangeRecursive(const KisNode *node, int time, KisTimeRange &range, bool exclusive); + static KisTimeRange calculateIdenticalFramesRecursive(const KisNode *node, int time); + static KisTimeRange calculateAffectedFramesRecursive(const KisNode *node, int time); static KisTimeRange calculateNodeIdenticalFrames(const KisNode *node, int time); static KisTimeRange calculateNodeAffectedFrames(const KisNode *node, int time); private: int m_start; int m_end; }; namespace KisDomUtils { void KRITAIMAGE_EXPORT saveValue(QDomElement *parent, const QString &tag, const KisTimeRange &range); bool KRITAIMAGE_EXPORT loadValue(const QDomElement &parent, const QString &tag, KisTimeRange *range); } Q_DECLARE_METATYPE(KisTimeRange); KRITAIMAGE_EXPORT QDebug operator<<(QDebug dbg, const KisTimeRange &r); #endif /* __KIS_TIME_RANGE_H */ diff --git a/libs/image/kis_transparency_mask.cc b/libs/image/kis_transparency_mask.cc index ac73da4fd7..35ef408a4a 100644 --- a/libs/image/kis_transparency_mask.cc +++ b/libs/image/kis_transparency_mask.cc @@ -1,105 +1,110 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_transparency_mask.h" #include "kis_debug.h" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" KisTransparencyMask::KisTransparencyMask() : KisEffectMask() { } KisTransparencyMask::KisTransparencyMask(const KisTransparencyMask& rhs) : KisEffectMask(rhs) { } KisTransparencyMask::~KisTransparencyMask() { } QRect KisTransparencyMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const { Q_UNUSED(maskPos); if (src != dst) { KisPainter::copyAreaOptimized(rc.topLeft(), src, dst, rc); src->fill(rc, KoColor(Qt::transparent, src->colorSpace())); } return rc; } QRect KisTransparencyMask::extent() const { return parent() ? parent()->extent() : QRect(); } QRect KisTransparencyMask::exactBounds() const { return parent() ? parent()->exactBounds() : QRect(); } QRect KisTransparencyMask::changeRect(const QRect &rect, PositionToFilthy pos) const { /** * Selection on transparency masks have no special meaning: * They do crop both: change and need area */ return KisMask::changeRect(rect, pos); } QRect KisTransparencyMask::needRect(const QRect &rect, PositionToFilthy pos) const { /** * Selection on transparency masks have no special meaning: * They do crop both: change and need area */ return KisMask::needRect(rect, pos); } +bool KisTransparencyMask::paintsOutsideSelection() const +{ + return true; +} + QIcon KisTransparencyMask::icon() const { return KisIconUtils::loadIcon("transparencyMask"); } bool KisTransparencyMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisTransparencyMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } diff --git a/libs/image/kis_transparency_mask.h b/libs/image/kis_transparency_mask.h index c231787d72..4c4677d17e 100644 --- a/libs/image/kis_transparency_mask.h +++ b/libs/image/kis_transparency_mask.h @@ -1,64 +1,66 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_TRANSPARENCY_MASK_ #define _KIS_TRANSPARENCY_MASK_ #include "kis_types.h" #include "kis_effect_mask.h" class QRect; /** * A transparency mask is a single channel mask that applies a particular * transparency to the layer the mask belongs to. It differs from an * adjustment layer in that it only works on its parent layer, while * adjustment layers work on all layers below it in its layer group. * * XXX: Use KisConfig::useProjections() to enable/disable the caching of * the projection. */ class KRITAIMAGE_EXPORT KisTransparencyMask : public KisEffectMask { Q_OBJECT public: KisTransparencyMask(); KisTransparencyMask(const KisTransparencyMask& rhs); ~KisTransparencyMask() override; KisNodeSP clone() const override { return KisNodeSP(new KisTransparencyMask(*this)); } QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const override; QIcon icon() const override; bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; QRect extent() const override; QRect exactBounds() const override; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; + + bool paintsOutsideSelection() const override; }; #endif //_KIS_TRANSPARENCY_MASK_ diff --git a/libs/image/kis_update_scheduler.cpp b/libs/image/kis_update_scheduler.cpp index 33d77d467a..68b5568ff7 100644 --- a/libs/image/kis_update_scheduler.cpp +++ b/libs/image/kis_update_scheduler.cpp @@ -1,503 +1,503 @@ /* * 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 "kis_update_scheduler.h" #include "klocalizedstring.h" #include "kis_image_config.h" #include "kis_merge_walker.h" #include "kis_full_refresh_walker.h" #include "kis_updater_context.h" #include "kis_simple_update_queue.h" #include "kis_strokes_queue.h" #include "kis_queues_progress_updater.h" -#include "KisUpdateSchedulerConfigNotifier.h" +#include "KisImageConfigNotifier.h" #include #include "kis_lazy_wait_condition.h" #include //#define DEBUG_BALANCING #ifdef DEBUG_BALANCING #define DEBUG_BALANCING_METRICS(decidedFirst, excl) \ dbgKrita << "Balance decision:" << decidedFirst \ << "(" << excl << ")" \ << "updates:" << m_d->updatesQueue.sizeMetric() \ << "strokes:" << m_d->strokesQueue.sizeMetric() #else #define DEBUG_BALANCING_METRICS(decidedFirst, excl) #endif struct Q_DECL_HIDDEN KisUpdateScheduler::Private { Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p) : q(_q) , updaterContext(KisImageConfig(true).maxNumberOfThreads(), q) , projectionUpdateListener(p) {} KisUpdateScheduler *q; KisSimpleUpdateQueue updatesQueue; KisStrokesQueue strokesQueue; KisUpdaterContext updaterContext; bool processingBlocked = false; qreal defaultBalancingRatio = 1.0; // desired strokes-queue-size / updates-queue-size KisProjectionUpdateListener *projectionUpdateListener; KisQueuesProgressUpdater *progressUpdater = 0; QAtomicInt updatesLockCounter; QReadWriteLock updatesStartLock; KisLazyWaitCondition updatesFinishedCondition; qreal balancingRatio() const { const qreal strokeRatioOverride = strokesQueue.balancingRatioOverride(); return strokeRatioOverride > 0 ? strokeRatioOverride : defaultBalancingRatio; } }; KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent) : QObject(parent), m_d(new Private(this, projectionUpdateListener)) { updateSettings(); connectSignals(); } KisUpdateScheduler::KisUpdateScheduler() : m_d(new Private(this, 0)) { } KisUpdateScheduler::~KisUpdateScheduler() { delete m_d->progressUpdater; delete m_d; } void KisUpdateScheduler::setThreadsLimit(int value) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->processingBlocked); /** * Thread limit can be changed without the full-featured barrier * lock, we can avoid waiting for all the jobs to complete. We * should just ensure there is no more jobs in the updater context. */ lock(); m_d->updaterContext.lock(); m_d->updaterContext.setThreadsLimit(value); m_d->updaterContext.unlock(); unlock(false); } int KisUpdateScheduler::threadsLimit() const { std::lock_guard l(m_d->updaterContext); return m_d->updaterContext.threadsLimit(); } void KisUpdateScheduler::connectSignals() { connect(&m_d->updaterContext, SIGNAL(sigContinueUpdate(const QRect&)), SLOT(continueUpdate(const QRect&)), Qt::DirectConnection); connect(&m_d->updaterContext, SIGNAL(sigDoSomeUsefulWork()), SLOT(doSomeUsefulWork()), Qt::DirectConnection); connect(&m_d->updaterContext, SIGNAL(sigSpareThreadAppeared()), SLOT(spareThreadAppeared()), Qt::DirectConnection); - connect(KisUpdateSchedulerConfigNotifier::instance(), SIGNAL(configChanged()), + connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings())); } void KisUpdateScheduler::setProgressProxy(KoProgressProxy *progressProxy) { delete m_d->progressUpdater; m_d->progressUpdater = progressProxy ? new KisQueuesProgressUpdater(progressProxy, this) : 0; } void KisUpdateScheduler::progressUpdate() { if (!m_d->progressUpdater) return; if(!m_d->strokesQueue.hasOpenedStrokes()) { QString jobName = m_d->strokesQueue.currentStrokeName().toString(); if(jobName.isEmpty()) { jobName = i18n("Updating..."); } int sizeMetric = m_d->strokesQueue.sizeMetric(); if (!sizeMetric) { sizeMetric = m_d->updatesQueue.sizeMetric(); } m_d->progressUpdater->updateProgress(sizeMetric, jobName); } else { m_d->progressUpdater->hide(); } } void KisUpdateScheduler::updateProjection(KisNodeSP node, const QVector &rects, const QRect &cropRect) { m_d->updatesQueue.addUpdateJob(node, rects, cropRect, currentLevelOfDetail()); processQueues(); } void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect) { m_d->updatesQueue.addUpdateJob(node, rc, cropRect, currentLevelOfDetail()); processQueues(); } void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect) { m_d->updatesQueue.addUpdateNoFilthyJob(node, rc, cropRect, currentLevelOfDetail()); processQueues(); } void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QRect& rc, const QRect &cropRect) { m_d->updatesQueue.addFullRefreshJob(root, rc, cropRect, currentLevelOfDetail()); processQueues(); } void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect) { KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(cropRect); walker->collectRects(root, rc); bool needLock = true; if(m_d->processingBlocked) { warnImage << "WARNING: Calling synchronous fullRefresh under a scheduler lock held"; warnImage << "We will not assert for now, but please port caller's to strokes"; warnImage << "to avoid this warning"; needLock = false; } if(needLock) lock(); m_d->updaterContext.lock(); Q_ASSERT(m_d->updaterContext.isJobAllowed(walker)); m_d->updaterContext.addMergeJob(walker); m_d->updaterContext.waitForDone(); m_d->updaterContext.unlock(); if(needLock) unlock(true); } void KisUpdateScheduler::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->updatesQueue.addSpontaneousJob(spontaneousJob); processQueues(); } KisStrokeId KisUpdateScheduler::startStroke(KisStrokeStrategy *strokeStrategy) { KisStrokeId id = m_d->strokesQueue.startStroke(strokeStrategy); processQueues(); return id; } void KisUpdateScheduler::addJob(KisStrokeId id, KisStrokeJobData *data) { m_d->strokesQueue.addJob(id, data); processQueues(); } void KisUpdateScheduler::endStroke(KisStrokeId id) { m_d->strokesQueue.endStroke(id); processQueues(); } bool KisUpdateScheduler::cancelStroke(KisStrokeId id) { bool result = m_d->strokesQueue.cancelStroke(id); processQueues(); return result; } bool KisUpdateScheduler::tryCancelCurrentStrokeAsync() { return m_d->strokesQueue.tryCancelCurrentStrokeAsync(); } UndoResult KisUpdateScheduler::tryUndoLastStrokeAsync() { return m_d->strokesQueue.tryUndoLastStrokeAsync(); } bool KisUpdateScheduler::wrapAroundModeSupported() const { return m_d->strokesQueue.wrapAroundModeSupported(); } void KisUpdateScheduler::setDesiredLevelOfDetail(int lod) { m_d->strokesQueue.setDesiredLevelOfDetail(lod); /** * The queue might have started an internal stroke for * cache synchronization. Process the queues to execute * it if needed. */ processQueues(); } void KisUpdateScheduler::explicitRegenerateLevelOfDetail() { m_d->strokesQueue.explicitRegenerateLevelOfDetail(); // \see a comment in setDesiredLevelOfDetail() processQueues(); } int KisUpdateScheduler::currentLevelOfDetail() const { int levelOfDetail = m_d->updaterContext.currentLevelOfDetail(); if (levelOfDetail < 0) { levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail(); } if (levelOfDetail < 0) { levelOfDetail = 0; } return levelOfDetail; } void KisUpdateScheduler::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory) { m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory); } void KisUpdateScheduler::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory) { m_d->strokesQueue.setSuspendUpdatesStrokeStrategyFactory(factory); } void KisUpdateScheduler::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory) { m_d->strokesQueue.setResumeUpdatesStrokeStrategyFactory(factory); } KisPostExecutionUndoAdapter *KisUpdateScheduler::lodNPostExecutionUndoAdapter() const { return m_d->strokesQueue.lodNPostExecutionUndoAdapter(); } void KisUpdateScheduler::updateSettings() { m_d->updatesQueue.updateSettings(); KisImageConfig config(true); m_d->defaultBalancingRatio = config.schedulerBalancingRatio(); setThreadsLimit(config.maxNumberOfThreads()); } void KisUpdateScheduler::lock() { m_d->processingBlocked = true; m_d->updaterContext.waitForDone(); } void KisUpdateScheduler::unlock(bool resetLodLevels) { if (resetLodLevels) { /** * Legacy strokes may have changed the image while we didn't * control it. Notify the queue to take it into account. */ m_d->strokesQueue.notifyUFOChangedImage(); } m_d->processingBlocked = false; processQueues(); } bool KisUpdateScheduler::isIdle() { bool result = false; if (tryBarrierLock()) { result = true; unlock(false); } return result; } void KisUpdateScheduler::waitForDone() { do { processQueues(); m_d->updaterContext.waitForDone(); } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()); } bool KisUpdateScheduler::tryBarrierLock() { if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) { return false; } m_d->processingBlocked = true; m_d->updaterContext.waitForDone(); if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) { m_d->processingBlocked = false; processQueues(); return false; } return true; } void KisUpdateScheduler::barrierLock() { do { m_d->processingBlocked = false; processQueues(); m_d->processingBlocked = true; m_d->updaterContext.waitForDone(); } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()); } void KisUpdateScheduler::processQueues() { wakeUpWaitingThreads(); if(m_d->processingBlocked) return; if(m_d->strokesQueue.needsExclusiveAccess()) { DEBUG_BALANCING_METRICS("STROKES", "X"); m_d->strokesQueue.processQueue(m_d->updaterContext, !m_d->updatesQueue.isEmpty()); if(!m_d->strokesQueue.needsExclusiveAccess()) { tryProcessUpdatesQueue(); } } else if(m_d->balancingRatio() * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) { DEBUG_BALANCING_METRICS("STROKES", "N"); m_d->strokesQueue.processQueue(m_d->updaterContext, !m_d->updatesQueue.isEmpty()); tryProcessUpdatesQueue(); } else { DEBUG_BALANCING_METRICS("UPDATES", "N"); tryProcessUpdatesQueue(); m_d->strokesQueue.processQueue(m_d->updaterContext, !m_d->updatesQueue.isEmpty()); } progressUpdate(); } void KisUpdateScheduler::blockUpdates() { m_d->updatesFinishedCondition.initWaiting(); m_d->updatesLockCounter.ref(); while(haveUpdatesRunning()) { m_d->updatesFinishedCondition.wait(); } m_d->updatesFinishedCondition.endWaiting(); } void KisUpdateScheduler::unblockUpdates() { m_d->updatesLockCounter.deref(); processQueues(); } void KisUpdateScheduler::wakeUpWaitingThreads() { if(m_d->updatesLockCounter && !haveUpdatesRunning()) { m_d->updatesFinishedCondition.wakeAll(); } } void KisUpdateScheduler::tryProcessUpdatesQueue() { QReadLocker locker(&m_d->updatesStartLock); if(m_d->updatesLockCounter) return; m_d->updatesQueue.processQueue(m_d->updaterContext); } bool KisUpdateScheduler::haveUpdatesRunning() { QWriteLocker locker(&m_d->updatesStartLock); qint32 numMergeJobs, numStrokeJobs; m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs); return numMergeJobs; } void KisUpdateScheduler::continueUpdate(const QRect &rect) { Q_ASSERT(m_d->projectionUpdateListener); m_d->projectionUpdateListener->notifyProjectionUpdated(rect); } void KisUpdateScheduler::doSomeUsefulWork() { m_d->updatesQueue.optimize(); } void KisUpdateScheduler::spareThreadAppeared() { processQueues(); } KisTestableUpdateScheduler::KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, qint32 threadCount) { Q_UNUSED(threadCount); updateSettings(); m_d->projectionUpdateListener = projectionUpdateListener; // The queue will update settings in a constructor itself // m_d->updatesQueue = new KisTestableSimpleUpdateQueue(); // m_d->strokesQueue = new KisStrokesQueue(); // m_d->updaterContext = new KisTestableUpdaterContext(threadCount); connectSignals(); } KisTestableUpdaterContext* KisTestableUpdateScheduler::updaterContext() { return dynamic_cast(&m_d->updaterContext); } KisTestableSimpleUpdateQueue* KisTestableUpdateScheduler::updateQueue() { return dynamic_cast(&m_d->updatesQueue); } diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp index 079f4b1585..67d596377f 100644 --- a/libs/image/lazybrush/kis_colorize_mask.cpp +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -1,1167 +1,1168 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorize_mask.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_icon_utils.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_painter.h" #include "kis_fill_painter.h" #include "kis_lazy_fill_tools.h" #include "kis_cached_paint_device.h" #include "kis_paint_device_debug_utils.h" #include "kis_layer_properties_icons.h" #include "kis_thread_safe_signal_compressor.h" #include "kis_colorize_stroke_strategy.h" #include "kis_multiway_cut.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_macro_based_undo_store.h" #include "kis_post_execution_undo_adapter.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "krita_utils.h" using namespace KisLazyFillTools; struct KisColorizeMask::Private { Private(KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), fakePaintDevice(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), filteredSource(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), needAddCurrentKeyStroke(false), showKeyStrokes(true), showColoring(true), needsUpdate(true), originalSequenceNumber(-1), updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), updateIsRunning(false), filteringOptions(false, 4.0, 15, 0.7), limitToDeviceBounds(false) { } Private(const Private &rhs, KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(*rhs.coloringProjection)), fakePaintDevice(new KisPaintDevice(*rhs.fakePaintDevice)), filteredSource(new KisPaintDevice(*rhs.filteredSource)), filteredDeviceBounds(rhs.filteredDeviceBounds), needAddCurrentKeyStroke(rhs.needAddCurrentKeyStroke), showKeyStrokes(rhs.showKeyStrokes), showColoring(rhs.showColoring), needsUpdate(false), originalSequenceNumber(-1), updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), offset(rhs.offset), updateIsRunning(false), filteringOptions(rhs.filteringOptions), limitToDeviceBounds(rhs.limitToDeviceBounds) { Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) { keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent); } } KisColorizeMask *q = 0; QList keyStrokes; KisPaintDeviceSP coloringProjection; KisPaintDeviceSP fakePaintDevice; KisPaintDeviceSP filteredSource; QRect filteredDeviceBounds; KoColor currentColor; KisPaintDeviceSP currentKeyStrokeDevice; bool needAddCurrentKeyStroke; bool showKeyStrokes; bool showColoring; KisCachedSelection cachedSelection; KisCachedSelection cachedConversionSelection; bool needsUpdate; int originalSequenceNumber; KisThreadSafeSignalCompressor updateCompressor; KisThreadSafeSignalCompressor dirtyParentUpdateCompressor; KisThreadSafeSignalCompressor prefilterRecalculationCompressor; QPoint offset; bool updateIsRunning; QStack extentBeforeUpdateStart; FilteringOptions filteringOptions; bool filteringDirty = true; bool limitToDeviceBounds = false; bool filteredSourceValid(KisPaintDeviceSP parentDevice) { return !filteringDirty && originalSequenceNumber == parentDevice->sequenceNumber(); } void setNeedsUpdateImpl(bool value, bool requestedByUser); bool shouldShowFilteredSource() const; bool shouldShowColoring() const; }; KisColorizeMask::KisColorizeMask() : m_d(new Private(this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); connect(&m_d->prefilterRecalculationCompressor, SIGNAL(timeout()), SLOT(slotRecalculatePrefilteredImage())); m_d->updateCompressor.moveToThread(qApp->thread()); } KisColorizeMask::~KisColorizeMask() { } KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs) : KisEffectMask(rhs), m_d(new Private(*rhs.m_d, this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); m_d->updateCompressor.moveToThread(qApp->thread()); } void KisColorizeMask::initializeCompositeOp() { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer || !parentLayer->original()) return; KisImageSP image = parentLayer->image(); if (!image) return; const qreal samplePortion = 0.1; const qreal alphaPortion = KritaUtils::estimatePortionOfTransparentPixels(parentLayer->original(), image->bounds(), samplePortion); setCompositeOpId(alphaPortion > 0.3 ? COMPOSITE_BEHIND : COMPOSITE_MULT); } const KoColorSpace* KisColorizeMask::colorSpace() const { return m_d->fakePaintDevice->colorSpace(); } struct SetKeyStrokesColorSpaceCommand : public KUndo2Command { SetKeyStrokesColorSpaceCommand(const KoColorSpace *dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, QList *list, KisColorizeMaskSP node) : m_dstCS(dstCS), m_renderingIntent(renderingIntent), m_conversionFlags(conversionFlags), m_list(list), m_node(node) {} void undo() override { KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_oldColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_oldColors[i]; } m_node->setNeedsUpdate(true); } void redo() override { if (m_oldColors.isEmpty()) { Q_FOREACH(const KeyStroke &stroke, *m_list) { m_oldColors << stroke.color; m_newColors << stroke.color; m_newColors.last().convertTo(m_dstCS, m_renderingIntent, m_conversionFlags); } } KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_newColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_newColors[i]; } m_node->setNeedsUpdate(true); } private: QVector m_oldColors; QVector m_newColors; const KoColorSpace *m_dstCS; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setProfile(const KoColorProfile *profile) { // WARNING: there is no undo information, used only while loading! m_d->fakePaintDevice->setProfile(profile); m_d->coloringProjection->setProfile(profile); for (auto stroke : m_d->keyStrokes) { stroke.color.setProfile(profile); } } KUndo2Command* KisColorizeMask::setColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisCommandUtils; CompositeCommand *composite = new CompositeCommand(); composite->addCommand(m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags)); composite->addCommand(m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags)); KUndo2Command *strokesConversionCommand = new SetKeyStrokesColorSpaceCommand( dstColorSpace, renderingIntent, conversionFlags, &m_d->keyStrokes, KisColorizeMaskSP(this)); strokesConversionCommand->redo(); composite->addCommand(new SkipFirstRedoWrapper(strokesConversionCommand)); return composite; } bool KisColorizeMask::needsUpdate() const { return m_d->needsUpdate; } void KisColorizeMask::setNeedsUpdate(bool value) { m_d->setNeedsUpdateImpl(value, true); } void KisColorizeMask::Private::setNeedsUpdateImpl(bool value, bool requestedByUser) { if (value != needsUpdate) { needsUpdate = value; q->baseNodeChangedCallback(); if (!value && requestedByUser) { updateCompressor.start(); } } } void KisColorizeMask::slotUpdateRegenerateFilling(bool prefilterOnly) { KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); const bool filteredSourceValid = m_d->filteredSourceValid(src); m_d->originalSequenceNumber = src->sequenceNumber(); m_d->filteringDirty = false; if (!prefilterOnly) { m_d->coloringProjection->clear(); } KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return; KisImageSP image = parentLayer->image(); if (image) { m_d->updateIsRunning = true; QRect fillBounds; if (m_d->limitToDeviceBounds) { fillBounds |= src->exactBounds(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { fillBounds |= stroke.dev->exactBounds(); } fillBounds &= image->bounds(); } else { fillBounds = image->bounds(); } m_d->filteredDeviceBounds = fillBounds; KisColorizeStrokeStrategy *strategy = new KisColorizeStrokeStrategy(src, m_d->coloringProjection, m_d->filteredSource, filteredSourceValid, fillBounds, this, prefilterOnly); strategy->setFilteringOptions(m_d->filteringOptions); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const KoColor color = !stroke.isTransparent ? stroke.color : KoColor(Qt::transparent, stroke.color.colorSpace()); strategy->addKeyStroke(stroke.dev, color); } m_d->extentBeforeUpdateStart.push(extent()); connect(strategy, SIGNAL(sigFinished(bool)), SLOT(slotRegenerationFinished(bool))); connect(strategy, SIGNAL(sigCancelled()), SLOT(slotRegenerationCancelled())); KisStrokeId id = image->startStroke(strategy); image->endStroke(id); } } void KisColorizeMask::slotUpdateOnDirtyParent() { KIS_ASSERT_RECOVER_RETURN(parent()); KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { const QRect &oldExtent = extent(); m_d->setNeedsUpdateImpl(true, false); m_d->filteringDirty = true; setDirty(oldExtent | extent()); } } void KisColorizeMask::slotRecalculatePrefilteredImage() { slotUpdateRegenerateFilling(true); } void KisColorizeMask::slotRegenerationFinished(bool prefilterOnly) { m_d->updateIsRunning = false; if (!prefilterOnly) { m_d->setNeedsUpdateImpl(false, false); } QRect oldExtent; if (!m_d->extentBeforeUpdateStart.isEmpty()) { oldExtent = m_d->extentBeforeUpdateStart.pop(); } else { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->extentBeforeUpdateStart.isEmpty()); // always fail! } setDirty(oldExtent | extent()); } void KisColorizeMask::slotRegenerationCancelled() { slotRegenerationFinished(true); } KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisMask::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeEditKeyStrokes, showKeyStrokes()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring()); return l; } void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisMask::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) { if (m_d->needsUpdate && m_d->needsUpdate != property.state.toBool()) { setNeedsUpdate(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeEditKeyStrokes.id()) { if (m_d->showKeyStrokes != property.state.toBool()) { setShowKeyStrokes(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) { if (m_d->showColoring != property.state.toBool()) { setShowColoring(property.state.toBool()); } } } } KisPaintDeviceSP KisColorizeMask::paintDevice() const { return m_d->showKeyStrokes && !m_d->updateIsRunning ? m_d->fakePaintDevice : KisPaintDeviceSP(); } KisPaintDeviceSP KisColorizeMask::coloringProjection() const { return m_d->coloringProjection; } KisPaintDeviceSP KisColorizeMask::colorPickSourceDevice() const { return m_d->shouldShowColoring() && !m_d->coloringProjection->extent().isEmpty() ? m_d->coloringProjection : projection(); } QIcon KisColorizeMask::icon() const { return KisIconUtils::loadIcon("colorizeMask"); } bool KisColorizeMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } bool KisColorizeMask::Private::shouldShowFilteredSource() const { return !updateIsRunning && showKeyStrokes && !filteringDirty && filteredSource && !filteredSource->extent().isEmpty(); } bool KisColorizeMask::Private::shouldShowColoring() const { return !updateIsRunning && showColoring && coloringProjection; } QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect &rect, PositionToFilthy maskPos) const { Q_UNUSED(maskPos); if (maskPos == N_ABOVE_FILTHY) { // the source layer has changed, we should update the filtered cache! if (!m_d->filteringDirty) { emit sigUpdateOnDirtyParent(); } } KIS_ASSERT(dst != src); // Draw the filling and the original layer { KisPainter gc(dst); if (m_d->shouldShowFilteredSource()) { const QRect drawRect = m_d->limitToDeviceBounds ? rect & m_d->filteredDeviceBounds : rect; gc.setOpacity(128); gc.bitBlt(drawRect.topLeft(), m_d->filteredSource, drawRect); } else { gc.setOpacity(255); gc.bitBlt(rect.topLeft(), src, rect); } if (m_d->shouldShowColoring()) { gc.setOpacity(opacity()); gc.setCompositeOp(compositeOpId()); gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect); } } // Draw the key strokes if (m_d->showKeyStrokes) { KisIndirectPaintingSupport::ReadLocker locker(this); KisSelectionSP selection = m_d->cachedSelection.getSelection(); KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisFillPainter gc(dst); QList extendedStrokes = m_d->keyStrokes; if (m_d->currentKeyStrokeDevice && m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { extendedStrokes << KeyStroke(m_d->currentKeyStrokeDevice, m_d->currentColor); } Q_FOREACH (const KeyStroke &stroke, extendedStrokes) { selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); if (stroke.color == m_d->currentColor || (isTemporaryTargetErasing && temporaryExtent.intersects(selection->pixelSelection()->selectedRect()))) { if (temporaryTarget) { tempSelection->copyAlphaFrom(temporaryTarget, rect); KisPainter selectionPainter(selection->pixelSelection()); setupTemporaryPainter(&selectionPainter); selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect); } } gc.fillSelection(rect, stroke.color); } m_d->cachedSelection.putSelection(selection); m_d->cachedSelection.putSelection(conversionSelection); } return rect; } struct DeviceExtentPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->extent(); } }; struct DeviceExactBoundsPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->exactBounds(); } }; template QRect KisColorizeMask::calculateMaskBounds(DeviceMetricPolicy boundsPolicy) const { QRect rc; if (m_d->shouldShowFilteredSource()) { rc |= boundsPolicy(m_d->filteredSource); } if (m_d->shouldShowColoring()) { rc |= boundsPolicy(m_d->coloringProjection); } if (m_d->showKeyStrokes) { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { rc |= boundsPolicy(stroke.dev); } KisIndirectPaintingSupport::ReadLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { rc |= boundsPolicy(temporaryTarget); } } return rc; } QRect KisColorizeMask::extent() const { return calculateMaskBounds(DeviceExtentPolicy()); } QRect KisColorizeMask::exactBounds() const { return calculateMaskBounds(DeviceExactBoundsPolicy()); } QRect KisColorizeMask::nonDependentExtent() const { return extent(); } KisImageSP KisColorizeMask::fetchImage() const { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return KisImageSP(); return parentLayer->image(); } void KisColorizeMask::setImage(KisImageWSP image) { KisDefaultBoundsSP bounds(new KisDefaultBounds(image)); auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { it->dev->setDefaultBounds(bounds); } m_d->coloringProjection->setDefaultBounds(bounds); m_d->fakePaintDevice->setDefaultBounds(bounds); m_d->filteredSource->setDefaultBounds(bounds); } void KisColorizeMask::setCurrentColor(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); WriteLocker locker(this); m_d->setNeedsUpdateImpl(true, false); QList::const_iterator it = std::find_if(m_d->keyStrokes.constBegin(), m_d->keyStrokes.constEnd(), [color] (const KeyStroke &s) { return s.color == color; }); KisPaintDeviceSP activeDevice; bool newKeyStroke = false; if (it == m_d->keyStrokes.constEnd()) { activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); activeDevice->setParentNode(this); activeDevice->setDefaultBounds(KisDefaultBoundsBaseSP(new KisDefaultBounds(fetchImage()))); newKeyStroke = true; } else { activeDevice = it->dev; } m_d->currentColor = color; m_d->currentKeyStrokeDevice = activeDevice; m_d->needAddCurrentKeyStroke = newKeyStroke; } struct KeyStrokeAddRemoveCommand : public KisCommandUtils::FlipFlopCommand { KeyStrokeAddRemoveCommand(bool add, int index, KeyStroke stroke, QList *list, KisColorizeMaskSP node) : FlipFlopCommand(!add), m_index(index), m_stroke(stroke), m_list(list), m_node(node) {} void init() override { m_list->insert(m_index, m_stroke); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } void end() override { KIS_ASSERT_RECOVER_RETURN((*m_list)[m_index] == m_stroke); m_list->removeAt(m_index); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } private: int m_index; KeyStroke m_stroke; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) { Q_UNUSED(layer); WriteLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisSavedMacroCommand *macro = undoAdapter->createMacro(transactionText); KisMacroBasedUndoStore store(macro); KisPostExecutionUndoAdapter fakeUndoAdapter(&store, undoAdapter->strokesFacade()); /** * Add a new key stroke plane */ if (m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { KeyStroke key(m_d->currentKeyStrokeDevice, m_d->currentColor); KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( true, m_d->keyStrokes.size(), key, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } /** * When erasing, the brush affects all the key strokes, not only * the current one. */ if (!isTemporaryTargetErasing) { mergeToLayerImpl(m_d->currentKeyStrokeDevice, &fakeUndoAdapter, transactionText, timedID, false); } else { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { if (temporaryExtent.intersects(stroke.dev->extent())) { mergeToLayerImpl(stroke.dev, &fakeUndoAdapter, transactionText, timedID, false); } } } mergeToLayerImpl(m_d->fakePaintDevice, &fakeUndoAdapter, transactionText, timedID, false); m_d->currentKeyStrokeDevice = 0; m_d->currentColor = KoColor(); releaseResources(); /** * Try removing the key strokes that has been completely erased */ if (isTemporaryTargetErasing) { for (int index = 0; index < m_d->keyStrokes.size(); /*noop*/) { const KeyStroke &stroke = m_d->keyStrokes[index]; if (stroke.dev->exactBounds().isEmpty()) { KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( false, index, stroke, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } else { index++; } } } undoAdapter->addMacro(macro); } void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const bool nonAlphaDst = !(*painter->device()->colorSpace() == *alpha8); if (nonAlphaDst) { Q_FOREACH (const QRect &rc, src->region().rects()) { painter->bitBlt(rc.topLeft(), src, rc); } } else { KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); Q_FOREACH (const QRect &rc, src->region().rects()) { tempSelection->copyAlphaFrom(src, rc); painter->bitBlt(rc.topLeft(), tempSelection, rc); } m_d->cachedSelection.putSelection(conversionSelection); } } bool KisColorizeMask::supportsNonIndirectPainting() const { return false; } bool KisColorizeMask::showColoring() const { return m_d->showColoring; } void KisColorizeMask::setShowColoring(bool value) { QRect savedExtent; if (m_d->showColoring && !value) { savedExtent = extent(); } m_d->showColoring = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } } bool KisColorizeMask::showKeyStrokes() const { return m_d->showKeyStrokes; } void KisColorizeMask::setShowKeyStrokes(bool value) { QRect savedExtent; if (m_d->showKeyStrokes && !value) { savedExtent = extent(); } m_d->showKeyStrokes = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } regeneratePrefilteredDeviceIfNeeded(); } KisColorizeMask::KeyStrokeColors KisColorizeMask::keyStrokesColors() const { KeyStrokeColors colors; // TODO: thread safety! for (int i = 0; i < m_d->keyStrokes.size(); i++) { colors.colors << m_d->keyStrokes[i].color; if (m_d->keyStrokes[i].isTransparent) { colors.transparentIndex = i; } } return colors; } struct SetKeyStrokeColorsCommand : public KUndo2Command { SetKeyStrokeColorsCommand(const QList newList, QList *list, KisColorizeMaskSP node) : m_newList(newList), m_oldList(*list), m_list(list), m_node(node) {} void redo() override { *m_list = m_newList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } void undo() override { *m_list = m_oldList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } private: QList m_newList; QList m_oldList; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setKeyStrokesColors(KeyStrokeColors colors) { KIS_ASSERT_RECOVER_RETURN(colors.colors.size() == m_d->keyStrokes.size()); QList newList = m_d->keyStrokes; for (int i = 0; i < newList.size(); i++) { newList[i].color = colors.colors[i]; newList[i].color.convertTo(colorSpace()); newList[i].isTransparent = colors.transparentIndex == i; } KisProcessingApplicator applicator(fetchImage(), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Change Key Stroke Color")); applicator.applyCommand( new SetKeyStrokeColorsCommand( newList, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } void KisColorizeMask::removeKeyStroke(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); QList::iterator it = std::find_if(m_d->keyStrokes.begin(), m_d->keyStrokes.end(), [color] (const KeyStroke &s) { return s.color == color; }); KIS_SAFE_ASSERT_RECOVER_RETURN(it != m_d->keyStrokes.end()); const int index = it - m_d->keyStrokes.begin(); KisProcessingApplicator applicator(KisImageWSP(fetchImage()), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Remove Key Stroke")); applicator.applyCommand( new KeyStrokeAddRemoveCommand( false, index, *it, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } QVector KisColorizeMask::allPaintDevices() const { QVector devices; Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { devices << stroke.dev; } devices << m_d->coloringProjection; devices << m_d->fakePaintDevice; return devices; } void KisColorizeMask::resetCache() { m_d->filteredSource->clear(); m_d->originalSequenceNumber = -1; m_d->filteringDirty = true; rerenderFakePaintDevice(); slotUpdateRegenerateFilling(true); } void KisColorizeMask::setUseEdgeDetection(bool value) { m_d->filteringOptions.useEdgeDetection = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::useEdgeDetection() const { return m_d->filteringOptions.useEdgeDetection; } void KisColorizeMask::setEdgeDetectionSize(qreal value) { m_d->filteringOptions.edgeDetectionSize = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::edgeDetectionSize() const { return m_d->filteringOptions.edgeDetectionSize; } void KisColorizeMask::setFuzzyRadius(qreal value) { m_d->filteringOptions.fuzzyRadius = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::fuzzyRadius() const { return m_d->filteringOptions.fuzzyRadius; } void KisColorizeMask::setCleanUpAmount(qreal value) { m_d->filteringOptions.cleanUpAmount = value; setNeedsUpdate(true); } qreal KisColorizeMask::cleanUpAmount() const { return m_d->filteringOptions.cleanUpAmount; } void KisColorizeMask::setLimitToDeviceBounds(bool value) { m_d->limitToDeviceBounds = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::limitToDeviceBounds() const { return m_d->limitToDeviceBounds; } void KisColorizeMask::rerenderFakePaintDevice() { m_d->fakePaintDevice->clear(); KisFillPainter gc(m_d->fakePaintDevice); KisSelectionSP selection = m_d->cachedSelection.getSelection(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const QRect rect = stroke.dev->extent(); selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); gc.fillSelection(rect, stroke.color); } m_d->cachedSelection.putSelection(selection); } void KisColorizeMask::testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent) { m_d->keyStrokes << KeyStroke(dev, color, isTransparent); } void KisColorizeMask::testingRegenerateMask() { slotUpdateRegenerateFilling(); + m_d->updateIsRunning = false; } KisPaintDeviceSP KisColorizeMask::testingFilteredSource() const { return m_d->filteredSource; } QList KisColorizeMask::fetchKeyStrokesDirect() const { return m_d->keyStrokes; } void KisColorizeMask::setKeyStrokesDirect(const QList &strokes) { m_d->keyStrokes = strokes; for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end(); ++it) { it->dev->setParentNode(this); } KisImageSP image = fetchImage(); KIS_SAFE_ASSERT_RECOVER_RETURN(image); setImage(image); } qint32 KisColorizeMask::x() const { return m_d->offset.x(); } qint32 KisColorizeMask::y() const { return m_d->offset.y(); } void KisColorizeMask::setX(qint32 x) { const QPoint oldOffset = m_d->offset; m_d->offset.rx() = x; moveAllInternalDevices(m_d->offset - oldOffset); } void KisColorizeMask::setY(qint32 y) { const QPoint oldOffset = m_d->offset; m_d->offset.ry() = y; moveAllInternalDevices(m_d->offset - oldOffset); } KisPaintDeviceList KisColorizeMask::getLodCapableDevices() const { KisPaintDeviceList list; auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { list << it->dev; } list << m_d->coloringProjection; list << m_d->fakePaintDevice; list << m_d->filteredSource; return list; } void KisColorizeMask::regeneratePrefilteredDeviceIfNeeded() { if (!parent()) return; KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { // update the prefiltered source if needed slotUpdateRegenerateFilling(true); } } void KisColorizeMask::moveAllInternalDevices(const QPoint &diff) { QVector devices = allPaintDevices(); Q_FOREACH (KisPaintDeviceSP dev, devices) { dev->moveTo(dev->offset() + diff); } } diff --git a/libs/image/metadata/kis_meta_data_entry.cc b/libs/image/metadata/kis_meta_data_entry.cc index 4ca9e21fa6..a95c50a14a 100644 --- a/libs/image/metadata/kis_meta_data_entry.cc +++ b/libs/image/metadata/kis_meta_data_entry.cc @@ -1,148 +1,150 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_entry.h" #include #include #include "kis_meta_data_value.h" #include "kis_meta_data_schema.h" using namespace KisMetaData; struct Q_DECL_HIDDEN Entry::Private { QString name; const Schema* schema; Value value; bool valid; }; Entry::Entry() : d(new Private) { d->schema = 0; d->valid = false; } Entry::Entry(const Schema* schema, QString name, const Value& value) : d(new Private) { Q_ASSERT(!name.isEmpty()); if (!isValidName(name)) { errKrita << "Invalid metadata name:" << name; d->name = QString("INVALID: %s").arg(name); } else { d->name = name; } d->schema = schema; d->value = value; d->valid = true; } Entry::Entry(const Entry& e) : d(new Private()) { d->valid = false; *this = e; } Entry::~Entry() { delete d; } QString Entry::name() const { return d->name; } const Schema* Entry::schema() const { Q_ASSERT(d->schema); return d->schema; } void Entry::setSchema(const KisMetaData::Schema* schema) { + Q_ASSERT(schema); d->schema = schema; } QString Entry::qualifiedName() const { + Q_ASSERT(d->schema); return d->schema->generateQualifiedName(d->name); } const Value& Entry::value() const { return d->value; } Value& Entry::value() { return d->value; } bool Entry::isValid() const { return d->valid; } bool Entry::isValidName(const QString& _name) { if (_name.length() < 1) { dbgMetaData << "Too small"; return false; } if (!_name[0].isLetter()) { dbgMetaData << _name << " doesn't start by a letter"; return false; } for (int i = 1; i < _name.length(); ++i) { QChar c = _name[i]; if (!c.isLetterOrNumber()) { dbgMetaData << _name << " " << i << "th character isn't a letter or a digit"; return false; } } return true; } bool Entry::operator==(const Entry& e) const { return qualifiedName() == e.qualifiedName(); } Entry& Entry::operator=(const Entry & e) { if (e.isValid()) { Q_ASSERT(!isValid() || *this == e); d->name = e.d->name; d->schema = e.d->schema; d->value = e.d->value; d->valid = true; } return *this; } QDebug operator<<(QDebug debug, const Entry &c) { debug.nospace() << "Name: " << c.name() << " Qualified name: " << c.qualifiedName() << " Value: " << c.value(); return debug.space(); } diff --git a/libs/image/metadata/kis_meta_data_schema_registry.cc b/libs/image/metadata/kis_meta_data_schema_registry.cc index 2172fda7b4..36725fccb9 100644 --- a/libs/image/metadata/kis_meta_data_schema_registry.cc +++ b/libs/image/metadata/kis_meta_data_schema_registry.cc @@ -1,109 +1,111 @@ /* * Copyright (c) 2007,2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_schema_registry.h" #include #include #include "kis_debug.h" #include "kis_meta_data_schema_p.h" using namespace KisMetaData; // ---- Schema Registry ---- // struct Q_DECL_HIDDEN SchemaRegistry::Private { static SchemaRegistry *singleton; QHash uri2Schema; QHash prefix2Schema; }; SchemaRegistry *SchemaRegistry::Private::singleton = 0; SchemaRegistry* SchemaRegistry::instance() { if (SchemaRegistry::Private::singleton == 0) { SchemaRegistry::Private::singleton = new SchemaRegistry(); } return SchemaRegistry::Private::singleton; } SchemaRegistry::SchemaRegistry() : d(new Private) { + KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); - QStringList schemasFilenames; - schemasFilenames += KoResourcePaths::findAllResources("metadata_schema", "*.schema"); + QStringList schemasFilenames = KoResourcePaths::findAllResources("metadata_schema", "*.schema"); + + qDebug() << "schemasFilenames" << schemasFilenames; Q_FOREACH (const QString& fileName, schemasFilenames) { Schema* schema = new Schema(); schema->d->load(fileName); if (schemaFromUri(schema->uri())) { errMetaData << "Schema already exist uri: " << schema->uri(); } else if (schemaFromPrefix(schema->prefix())) { errMetaData << "Schema already exist prefix: " << schema->prefix(); } else { d->uri2Schema[schema->uri()] = schema; d->prefix2Schema[schema->prefix()] = schema; } } // DEPRECATED WRITE A SCHEMA FOR EACH OF THEM create(Schema::MakerNoteSchemaUri, "mkn"); create(Schema::IPTCSchemaUri, "Iptc4xmpCore"); create(Schema::PhotoshopSchemaUri, "photoshop"); } SchemaRegistry::~SchemaRegistry() { delete d; } const Schema* SchemaRegistry::schemaFromUri(const QString & uri) const { return d->uri2Schema[uri]; } const Schema* SchemaRegistry::schemaFromPrefix(const QString & prefix) const { return d->prefix2Schema[prefix]; } const Schema* SchemaRegistry::create(const QString & uri, const QString & prefix) { // First search for the schema const Schema* schema = schemaFromUri(uri); if (schema) { return schema; } // Second search for the prefix schema = schemaFromPrefix(prefix); if (schema) { return 0; // A schema with the same prefix already exist } // The schema doesn't exist yet, create it Schema* nschema = new Schema(uri, prefix); d->uri2Schema[uri] = nschema; d->prefix2Schema[prefix] = nschema; return nschema; } diff --git a/libs/image/metadata/kis_meta_data_store.cc b/libs/image/metadata/kis_meta_data_store.cc index bfcf9dd2e1..d0939012d6 100644 --- a/libs/image/metadata/kis_meta_data_store.cc +++ b/libs/image/metadata/kis_meta_data_store.cc @@ -1,213 +1,216 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_store.h" #include #include #include "kis_meta_data_entry.h" #include "kis_meta_data_filter.h" #include "kis_meta_data_schema.h" #include "kis_meta_data_schema_registry.h" #include "kis_meta_data_value.h" using namespace KisMetaData; uint qHash(const Entry& e) { return qHash(e.qualifiedName()); } struct Q_DECL_HIDDEN Store::Private { QHash entries; }; Store::Store() : d(new Private) { } Store::Store(const Store& s) : d(new Private(*s.d)) { // TODO: reaffect all schemas } Store::~Store() { delete d; } void Store::copyFrom(const Store* store) { for (QHash::const_iterator entryIt = store->begin(); entryIt != store->end(); ++entryIt) { const Entry& entry = entryIt.value(); if (entry.value().type() != KisMetaData::Value::Invalid) { if (containsEntry(entry.qualifiedName())) { getEntry(entry.qualifiedName()).value() = entry.value(); } else { addEntry(entry); } } } } bool Store::addEntry(const Entry& entry) { Q_ASSERT(!entry.name().isEmpty()); if (d->entries.contains(entry.qualifiedName()) && d->entries[entry.qualifiedName()].isValid()) { dbgMetaData << "Entry" << entry.qualifiedName() << " already exists in the store, cannot be included twice"; return false; } d->entries.insert(entry.qualifiedName(), entry); return true; } bool Store::empty() const { return d->entries.isEmpty(); } bool Store::isEmpty() const { return d->entries.isEmpty(); } bool Store::containsEntry(const QString & entryKey) const { return d->entries.contains(entryKey); } bool Store::containsEntry(const QString & uri, const QString & entryName) const { const Schema* schema = SchemaRegistry::instance()->schemaFromUri(uri); return containsEntry(schema->generateQualifiedName(entryName)); } bool Store::containsEntry(const KisMetaData::Schema* schema, const QString & entryName) const { - return containsEntry(schema->generateQualifiedName(entryName)); + if (schema) { + return containsEntry(schema->generateQualifiedName(entryName)); + } + return false; } Entry& Store::getEntry(const QString & entryKey) { if (!d->entries.contains(entryKey)) { QStringList splitKey = entryKey.split(':'); QString prefix = splitKey[0]; splitKey.pop_front(); d->entries[entryKey] = Entry(SchemaRegistry::instance()->schemaFromPrefix(prefix), splitKey.join(":"), Value()); } return d->entries [entryKey]; } Entry& Store::getEntry(const QString & uri, const QString & entryName) { const Schema* schema = SchemaRegistry::instance()->schemaFromUri(uri); Q_ASSERT(schema); return getEntry(schema, entryName); } Entry& Store::getEntry(const KisMetaData::Schema* schema, const QString & entryName) { return getEntry(schema->generateQualifiedName(entryName)); } const Entry& Store::getEntry(const QString & entryKey) const { return d->entries[entryKey]; } const Entry& Store::getEntry(const QString & uri, const QString & entryName) const { const Schema* schema = SchemaRegistry::instance()->schemaFromUri(uri); Q_ASSERT(schema); return getEntry(schema, entryName); } const Entry& Store::getEntry(const KisMetaData::Schema* schema, const QString & entryName) const { return getEntry(schema->generateQualifiedName(entryName)); } void Store::removeEntry(const QString & entryKey) { d->entries.remove(entryKey); } void Store::removeEntry(const QString & uri, const QString & entryName) { const Schema* schema = SchemaRegistry::instance()->schemaFromUri(uri); Q_ASSERT(schema); removeEntry(schema, entryName); } void Store::removeEntry(const KisMetaData::Schema* schema, const QString & entryName) { removeEntry(schema->generateQualifiedName(entryName)); } const Value& Store::getValue(const QString & uri, const QString & entryName) const { return getEntry(uri, entryName).value(); } QHash::const_iterator Store::begin() const { return d->entries.constBegin(); } QHash::const_iterator Store::end() const { return d->entries.constEnd(); } void Store::debugDump() const { dbgMetaData << "=== Dumping MetaData Store ==="; dbgMetaData << " - Metadata (there are" << d->entries.size() << " entries)"; Q_FOREACH (const Entry& e, d->entries) { if (e.isValid()) { dbgMetaData << e; } else { dbgMetaData << "Invalid entry"; } } } void Store::applyFilters(const QList & filters) { dbgMetaData << "Apply " << filters.size() << " filters"; Q_FOREACH (const Filter* filter, filters) { filter->filter(this); } } QList Store::keys() const { return d->entries.keys(); } QList Store::entries() const { return d->entries.values(); } diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index aff2df122c..175d8e8d43 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,244 +1,243 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_fixed_point_maths_test.cpp kis_node_query_path_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp kis_mask_similarity_test.cpp KisMaskGeneratorBenchmark.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_layer_style_filter_environment_test.cpp TEST_NAME kritaimage-layer_style_filter_environment_test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) ecm_add_test(kis_asl_parser_test.cpp TEST_NAME kritalibpsd-asl_parser_test LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test) ecm_add_test(KisPerStrokeRandomSourceTest.cpp TEST_NAME KisPerStrokeRandomSourceTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(KisWatershedWorkerTest.cpp TEST_NAME KisWatershedWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) # ecm_add_test(kis_dom_utils_test.cpp # TEST_NAME krita-image-DomUtils-Test # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc dep # kis_transform_worker_test.cpp # TEST_NAME krita-image-KisTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_perspective_transform_worker_test.cpp # TEST_NAME krita-image-KisPerspectiveTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kis_cs_conversion_test.cpp # TEST_NAME krita-image-KisCsConversionTest # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_processings_test.cpp # TEST_NAME krita-image-KisProcessingsTest #LINK_LIBRARIES kritaimage Qt5::Test) -# image/tests cannot use stuff that needs kisdocument -# kis_projection_leaf_test.cpp -# TEST_NAME kritaimage-projection_leaf_test -# LINK_LIBRARIES kritaimage Qt5::Test) +ecm_add_test(kis_projection_leaf_test.cpp + TEST_NAME KisProjectionLeafTest + LINK_LIBRARIES kritaimage Qt5::Test) if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_paint_device_test.cpp TEST_NAME krita-image-KisPaintDeviceTest LINK_LIBRARIES kritaimage kritaodf Qt5::Test) else() message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!") endif() if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_filter_mask_test.cpp TEST_NAME krita-image-KisFilterMaskTest LINK_LIBRARIES kritaimage Qt5::Test) else() message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!") endif() krita_add_broken_unit_test(kis_transform_mask_test.cpp TEST_NAME krita-image-KisTransformMaskTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_histogram_test.cpp TEST_NAME krita-image-KisHistogramTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_walkers_test.cpp TEST_NAME krita-image-KisWalkersTest LINK_LIBRARIES kritaimage Qt5::Test) #krita_add_broken_unit_test(kis_async_merger_test.cpp # TEST_NAME krita-image-KisAsyncMergerTest # LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_update_scheduler_test.cpp TEST_NAME krita-image-KisUpdateSchedulerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp TEST_NAME krita-image-KisQueuesProgressUpdaterTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp TEST_NAME krita-image-KisCageTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_meta_data_test.cpp TEST_NAME krita-image-KisMetaDataTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_random_generator_test.cpp TEST_NAME krita-image-KisRandomGeneratorTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_keyframing_test.cpp TEST_NAME krita-image-Keyframing-Test LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_image_animation_interface_test.cpp TEST_NAME krita-image-ImageAnimationInterface-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp TEST_NAME krita-image-OnionSkinCompositor-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_layer_styles_test.cpp TEST_NAME krita-image-LayerStylesTest LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.cpp b/libs/image/tests/KisMaskGeneratorBenchmark.cpp index 99b30aa4d2..a5dcdb1016 100644 --- a/libs/image/tests/KisMaskGeneratorBenchmark.cpp +++ b/libs/image/tests/KisMaskGeneratorBenchmark.cpp @@ -1,202 +1,202 @@ /* * Copyright (c) 2018 Iván Santa María * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "KisMaskGeneratorBenchmark.h" #include "kis_mask_similarity_test.h" #include "kis_brush_mask_applicator_base.h" #include "kis_mask_generator.h" #include "kis_cubic_curve.h" #include "krita_utils.h" #include "testutil.h" class KisMaskGeneratorBenchmarkTester { public: KisMaskGeneratorBenchmarkTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds) - : applicatorBase(_applicatorBase) - , m_bounds(_bounds) + : m_bounds(_bounds), + applicatorBase(_applicatorBase) { KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); m_paintDev->setRect(m_bounds); m_paintDev->initialize(255); MaskProcessingData data(m_paintDev, m_colorSpace, 0.0, 1.0, m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); // Start Benchmark applicatorBase->initializeData(&data); QElapsedTimer maskGenerationTime; maskGenerationTime.start(); QBENCHMARK { applicatorBase->process(m_bounds); } } protected: const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QRect m_bounds; KisBrushMaskApplicatorBase *applicatorBase; KisFixedPaintDeviceSP m_paintDev; }; void KisMaskGeneratorBenchmark::testDefaultScalarMask() { QRect bounds(0,0,1000,1000); { KisCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testDefaultVectorMask() { QRect bounds(0,0,1000,1000); { KisCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularGaussScalarMask() { QRect bounds(0,0,1000,1000); { KisGaussCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); circScalar.setDiameter(1000); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularGaussVectorMask() { QRect bounds(0,0,1000,1000); { KisGaussCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); circVectr.setDiameter(1000); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularSoftScalarMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circScalar.setSoftness(0.5); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularSoftVectorMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circVectr.setSoftness(0.5); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularScalarMask(){ QRect bounds(0,0,1000,1000); { KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true); rectScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(rectScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularVectorMask(){ QRect bounds(0,0,1000,1000); { KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true); KisMaskGeneratorBenchmarkTester(rectScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularGaussScalarMask() { QRect bounds(0,0,1000,1000); { KisGaussRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); // circScalar.setDiameter(1000); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularGaussVectorMask() { QRect bounds(0,0,1000,1000); { KisGaussRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); // circVectr.setDiameter(1000); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularSoftScalarMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularSoftVectorMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } QTEST_MAIN(KisMaskGeneratorBenchmark) diff --git a/libs/image/tests/kis_base_node_test.cpp b/libs/image/tests/kis_base_node_test.cpp index 3fa3bb3f9f..6ee8479963 100644 --- a/libs/image/tests/kis_base_node_test.cpp +++ b/libs/image/tests/kis_base_node_test.cpp @@ -1,181 +1,183 @@ /* * 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_base_node_test.h" #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_base_node.h" #include "kis_paint_device.h" #include "testutil.h" #include "kis_scalar_keyframe_channel.h" #include "KoColor.h" #include "kis_image_animation_interface.h" #include #include class TestNode : public TestUtil::DefaultNode { using KisBaseNode::accept; KisNodeSP clone() const override { return new TestNode(*this); } }; void KisBaseNodeTest::testCreation() { KisBaseNodeSP node = new TestNode(); QVERIFY(node->name().isEmpty()); QVERIFY(node->name() == node->objectName()); QVERIFY(node->icon().isNull()); QVERIFY(node->visible() == true); QVERIFY(node->userLocked() == false); QVERIFY(node->x() == 0); QVERIFY(node->y() == 0); } void KisBaseNodeTest::testContract() { KisBaseNodeSP node = new TestNode(); node->setName("bla"); QVERIFY(node->name() == "bla"); QVERIFY(node->objectName() == "bla"); node->setObjectName("zxc"); QVERIFY(node->name() == "zxc"); QVERIFY(node->objectName() == "zxc"); node->setVisible(!node->visible()); QVERIFY(node->visible() == false); node->setUserLocked(!node->userLocked()); QVERIFY(node->userLocked() == true); KisBaseNode::PropertyList list = node->sectionModelProperties(); QVERIFY(list.count() == 2); QVERIFY(list.at(0).state == node->visible()); QVERIFY(list.at(1).state == node->userLocked()); QImage image = node->createThumbnail(10, 10); QCOMPARE(image.size(), QSize(10, 10)); QVERIFY(image.pixel(5, 5) == QColor(0, 0, 0, 0).rgba()); } void KisBaseNodeTest::testProperties() { KisBaseNodeSP node = new TestNode(); { KoProperties props; props.setProperty("bladiebla", false); QVERIFY(node->check(props)); props.setProperty("visible", true); props.setProperty("locked", false); QVERIFY(node->check(props)); props.setProperty("locked", true); QVERIFY(!node->check(props)); node->setNodeProperty("locked", false); QVERIFY(node->userLocked() == false); } { KoProperties props; props.setProperty("blablabla", 10); node->mergeNodeProperties(props); QVERIFY(node->nodeProperties().intProperty("blablabla") == 10); QVERIFY(node->check(props)); props.setProperty("blablabla", 12); QVERIFY(!node->check(props)); } } void KisBaseNodeTest::testOpacityKeyframing() { TestUtil::MaskParent p; KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); p.image->addNode(layer2); KisPaintDeviceSP dev1 = p.layer->paintDevice(); dev1->fill(QRect(0,0,32,32), KoColor(Qt::red, dev1->colorSpace())); KisPaintDeviceSP dev2 = layer2->paintDevice(); dev2->fill(QRect(0,0,32,32), KoColor(Qt::green, dev2->colorSpace())); layer2->setOpacity(192); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), true); KisScalarKeyframeChannel *opacityChannel = dynamic_cast(channel); QVERIFY(opacityChannel); KisKeyframeSP key1 = opacityChannel->addKeyframe(7); opacityChannel->setScalarValue(key1, 128); KisKeyframeSP key2 = opacityChannel->addKeyframe(20); opacityChannel->setScalarValue(key2, 64); p.image->refreshGraph(); // No interpolation key1->setInterpolationMode(KisKeyframe::Constant); QColor sample; p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(63, 192, 0, 255)); - p.image->animationInterface()->requestTimeSwitchNonGUI(10); + p.image->animationInterface()->switchCurrentTimeAsync(10); p.image->waitForDone(); + p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(127, 128, 0, 255)); - p.image->animationInterface()->requestTimeSwitchNonGUI(30); + p.image->animationInterface()->switchCurrentTimeAsync(30); + p.image->waitForDone(); layer2->setOpacity(32); QCOMPARE(opacityChannel->scalarValue(key2), 32.0); p.image->waitForDone(); p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(223, 32, 0, 255)); // With interpolation key1->setInterpolationMode(KisKeyframe::Linear); key1->setInterpolationTangents(QPointF(), QPointF(0,0)); key2->setInterpolationTangents(QPointF(0,0), QPointF()); - p.image->animationInterface()->requestTimeSwitchNonGUI(10); + p.image->animationInterface()->switchCurrentTimeAsync(10); p.image->waitForDone(); p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(150, 105, 0, 255)); } QTEST_MAIN(KisBaseNodeTest) diff --git a/libs/image/tests/kis_colorize_mask_test.cpp b/libs/image/tests/kis_colorize_mask_test.cpp index ac8278a2a7..6260b3ae5a 100644 --- a/libs/image/tests/kis_colorize_mask_test.cpp +++ b/libs/image/tests/kis_colorize_mask_test.cpp @@ -1,214 +1,216 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorize_mask_test.h" #include #include "testutil.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_paint_device_debug_utils.h" #include "kis_global.h" #include "lazybrush/kis_lazy_fill_tools.h" #include "kundo2command.h" #include struct ColorizeMaskTester { ColorizeMaskTester() : refRect(0,0,200,200), p(refRect) { KisPaintDeviceSP src = p.layer->paintDevice(); fillRect = kisGrowRect(refRect, -20); internalFillRect = kisGrowRect(fillRect, -10); src->fill(fillRect, KoColor(Qt::black, src->colorSpace())); src->fill(internalFillRect, KoColor(Qt::transparent, src->colorSpace())); src->fill(QRect(100, 10, 10, 130), KoColor(Qt::black, src->colorSpace())); // KIS_DUMP_DEVICE_2(src, refRect, "src", "dd"); mask = new KisColorizeMask(); p.image->addNode(mask, p.layer); mask->initializeCompositeOp(); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, src->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, src->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, src->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } mask->resetCache(); mask->testingRegenerateMask(); p.image->waitForDone(); // KIS_DUMP_DEVICE_2(mask->coloringProjection(), refRect, "coloring", "dd"); // KIS_DUMP_DEVICE_2(mask->paintDevice(), refRect, "paintDevice", "dd"); // KIS_DUMP_DEVICE_2(mask->testingFilteredSource(), refRect, "filteredSource", "dd"); } QRect refRect; TestUtil::MaskParent p; KisColorizeMaskSP mask; QRect fillRect; QRect internalFillRect; }; void KisColorizeMaskTest::test() { ColorizeMaskTester t; QList strokes; strokes = t.mask->fetchKeyStrokesDirect(); + const QRect expectedFillRect(25,25,145,150); + // Check initial bounding rects QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(0,0,160,70)); - QCOMPARE(t.mask->coloringProjection()->exactBounds(), t.internalFillRect); + QCOMPARE(t.mask->coloringProjection()->exactBounds(), expectedFillRect); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); // Move the t.mask: the filled area is also expected to be moved! t.mask->setX(5); t.mask->setY(7); // KIS_DUMP_DEVICE_2(t.mask->coloringProjection(), t.refRect, "coloring2", "dd"); // KIS_DUMP_DEVICE_2(t.mask->paintDevice(), t.refRect, "paintDevice2", "dd"); // KIS_DUMP_DEVICE_2(t.mask->testingFilteredSource(), t.refRect, "filteredSource2", "dd"); QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(5,7,160,70)); - QCOMPARE(t.mask->coloringProjection()->exactBounds(), t.internalFillRect.translated(5, 7)); + QCOMPARE(t.mask->coloringProjection()->exactBounds(), expectedFillRect.translated(5, 7)); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); QCOMPARE(strokes[0].dev->exactBounds(), QRect(55,57,10,20)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(155,57,10,20)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(5,7,10,10)); // Test changing t.mask color space const KoColorSpace *oldCS = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *newCS = KoColorSpaceRegistry::instance()->lab16(); QCOMPARE(t.mask->colorSpace(), oldCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), oldCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), oldCS); QCOMPARE(strokes[0].color.colorSpace(), oldCS); QCOMPARE(strokes[1].color.colorSpace(), oldCS); QCOMPARE(strokes[2].color.colorSpace(), oldCS); KUndo2Command *cmd = t.mask->setColorSpace(newCS); cmd->redo(); strokes = t.mask->fetchKeyStrokesDirect(); QCOMPARE(t.mask->colorSpace(), newCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), newCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), newCS); QCOMPARE(strokes[0].color.colorSpace(), newCS); QCOMPARE(strokes[1].color.colorSpace(), newCS); QCOMPARE(strokes[2].color.colorSpace(), newCS); cmd->undo(); strokes = t.mask->fetchKeyStrokesDirect(); QCOMPARE(t.mask->colorSpace(), oldCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), oldCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), oldCS); QCOMPARE(strokes[0].color.colorSpace(), oldCS); QCOMPARE(strokes[1].color.colorSpace(), oldCS); QCOMPARE(strokes[2].color.colorSpace(), oldCS); cmd->redo(); strokes = t.mask->fetchKeyStrokesDirect(); QCOMPARE(t.mask->colorSpace(), newCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), newCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), newCS); QCOMPARE(strokes[0].color.colorSpace(), newCS); QCOMPARE(strokes[1].color.colorSpace(), newCS); QCOMPARE(strokes[2].color.colorSpace(), newCS); } #include "processing/kis_crop_processing_visitor.h" void KisColorizeMaskTest::testCrop() { ColorizeMaskTester t; QList strokes; strokes = t.mask->fetchKeyStrokesDirect(); // Check initial bounding rects QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(0,0,160,70)); QCOMPARE(t.mask->coloringProjection()->exactBounds(), t.internalFillRect); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); // KIS_DUMP_DEVICE_2(t.mask->coloringProjection(), t.refRect, "coloring3", "dd"); // KIS_DUMP_DEVICE_2(t.mask->paintDevice(), t.refRect, "paintDevice3", "dd"); // KIS_DUMP_DEVICE_2(t.mask->testingFilteredSource(), t.refRect, "filteredSource3", "dd"); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QRect cropRect(5,5,150,55); KisCropProcessingVisitor visitor(cropRect, true, true); t.mask->accept(visitor, t.p.image->undoAdapter()); // KIS_DUMP_DEVICE_2(t.mask->coloringProjection(), t.refRect, "coloring4", "dd"); // KIS_DUMP_DEVICE_2(t.mask->paintDevice(), t.refRect, "paintDevice4", "dd"); // KIS_DUMP_DEVICE_2(t.mask->testingFilteredSource(), t.refRect, "filteredSource4", "dd"); QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(0,0,150,55)); QCOMPARE(t.mask->coloringProjection()->exactBounds(), QRect(25,25,125,30)); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); QCOMPARE(strokes[0].dev->exactBounds(), QRect(45,45,10,10)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(145,45,5,10)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,5,5)); } QTEST_MAIN(KisColorizeMaskTest) diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index 79e6fd2432..e91907eafd 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1160 +1,1209 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "testutil.h" #include "kis_group_layer.h" #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } +#include "kis_paint_device_debug_utils.h" +#include "kis_algebra_2d.h" + +void KisImageTest::testPaintOverlayMask() +{ + QRect refRect(0, 0, 512, 512); + TestUtil::MaskParent p(refRect); + + QRect fillRect(50, 50, 412, 412); + QRect selectionRect(200, 200, 100, 50); + + KisPaintLayerSP layer1 = p.layer; + layer1->paintDevice()->fill(fillRect, KoColor(Qt::yellow, layer1->colorSpace())); + + KisSelectionMaskSP mask = new KisSelectionMask(p.image); + KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(layer1->paintDevice(), p.image)); + + selection->pixelSelection()->select(selectionRect, 128); + selection->pixelSelection()->select(KisAlgebra2D::blowRect(selectionRect,-0.3), 255); + + mask->setSelection(selection); + + //mask->setVisible(false); + //mask->setActive(false); + + p.image->addNode(mask, layer1); + + // a simple layer to disable oblidge child mechanism + KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "layer2", OPACITY_OPAQUE_U8); + p.image->addNode(layer2); + + p.image->initialRefreshGraph(); + + KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "00_initial", "dd"); + + p.image->setOverlaySelectionMask(mask); + p.image->waitForDone(); + + KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "01_activated", "dd"); + + p.image->setOverlaySelectionMask(0); + p.image->waitForDone(); + + KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "02_deactivated", "dd"); + + + +} + QTEST_MAIN(KisImageTest) diff --git a/libs/image/tests/kis_image_test.h b/libs/image/tests/kis_image_test.h index ce55321227..d5ec9d1112 100644 --- a/libs/image/tests/kis_image_test.h +++ b/libs/image/tests/kis_image_test.h @@ -1,62 +1,64 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_TESTER_H #define KIS_IMAGE_TESTER_H #include class KisImageTest : public QObject { Q_OBJECT private Q_SLOTS: void layerTests(); void benchmarkCreation(); void testBlockLevelOfDetail(); void testConvertImageColorSpace(); void testGlobalSelection(); void testCloneImage(); void testLayerComposition(); void testFlattenLayer(); void testMergeDown(); void testMergeDownDestinationInheritsAlpha(); void testMergeDownDestinationCustomCompositeOp(); void testMergeDownDestinationSameCompositeOpLayerStyle(); void testMergeDownDestinationSameCompositeOp(); void testMergeDownMultipleFrames(); void testMergeMultiple(); void testMergeCrossColorSpace(); void testMergeSelectionMasks(); void testFlattenImage(); void testFlattenPassThroughLayer(); void testMergeTwoPassThroughLayers(); void testMergePaintOverPassThroughLayer(); void testMergePassThroughOverPaintLayer(); + + void testPaintOverlayMask(); }; #endif diff --git a/libs/image/tests/kis_iterator_test.cpp b/libs/image/tests/kis_iterator_test.cpp index a80076f644..69692ac3dd 100644 --- a/libs/image/tests/kis_iterator_test.cpp +++ b/libs/image/tests/kis_iterator_test.cpp @@ -1,477 +1,479 @@ /* * 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_iterator_test.h" #include #include #include #include #include #include #include "kis_random_accessor_ng.h" #include "kis_random_sub_accessor.h" #include #include "kis_paint_device.h" #include +#include "sdk/tests/kistest.h" + void KisIteratorTest::allCsApplicator(void (KisIteratorTest::* funcPtr)(const KoColorSpace*cs)) { QList colorsapces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); Q_FOREACH (const KoColorSpace* cs, colorsapces) { dbgKrita << "Testing with" << cs->id(); if (cs->id() != "GRAYU16") // No point in testing extend for GRAYU16 (this->*funcPtr)(cs); } } inline quint8* allocatePixels(const KoColorSpace *colorSpace, int numPixels) { quint8 *bytes = new quint8[colorSpace->pixelSize() * 64 * 64 * 10]; KoColor color(Qt::red, colorSpace); const int pixelSize = colorSpace->pixelSize(); for(int i = 0; i < numPixels; i++) { memcpy(bytes + i * pixelSize, color.data(), pixelSize); } return bytes; } void KisIteratorTest::writeBytes(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); // Check allocation on tile boundaries // Allocate memory for a 2 * 5 tiles grid QScopedArrayPointer bytes(allocatePixels(colorSpace, 64 * 64 * 10)); // Covers 5 x 2 tiles dev.writeBytes(bytes.data(), 0, 0, 5 * 64, 2 * 64); // Covers QCOMPARE(dev.extent(), QRect(0, 0, 64 * 5, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 64 * 5, 64 * 2)); dev.clear(); QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); dev.clear(); // Covers three by three tiles dev.writeBytes(bytes.data(), 10, 10, 130, 130); QCOMPARE(dev.extent(), QRect(0, 0, 64 * 3, 64 * 3)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 130, 130)); dev.clear(); // Covers 11 x 2 tiles dev.writeBytes(bytes.data(), -10, -10, 10 * 64, 64); QCOMPARE(dev.extent(), QRect(-64, -64, 64 * 11, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(-10, -10, 640, 64)); } void KisIteratorTest::fill(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); dev.fill(0, 0, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 5, 5)); dev.clear(); dev.fill(5, 5, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 5, 5)); dev.clear(); dev.fill(5, 5, 500, 500, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 8 * 64, 8 * 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 500, 500)); dev.clear(); dev.fill(33, -10, 348, 1028, bytes.data()); QCOMPARE(dev.extent(), QRect(0, -64, 6 * 64, 17 * 64)); QCOMPARE(dev.exactBounds(), QRect(33, -10, 348, 1028)); } void KisIteratorTest::hLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisHLineConstIteratorSP cit = dev.createHLineConstIteratorNG(0, 0, 128); do {} while (cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(QPoint(0, 0), QPoint(-1, -1))); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); { dev.clear(); KisHLineIteratorSP it = dev.createHLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 1)); } dev.clear(); { KisHLineIteratorSP it = dev.createHLineIteratorNG(0, 1, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 1, 128, 1)); } dev.clear(); { KisHLineIteratorSP it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 192, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); } dev.clear(); dev.setX(10); dev.setY(-15); { KisHLineIteratorSP it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); } } void KisIteratorTest::vLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisVLineConstIteratorSP cit = dev.createVLineConstIteratorNG(0, 0, 128); do {} while (cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); { KisVLineIteratorSP it = dev.createVLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE((QRect) dev.extent(), QRect(0, 0, 64, 128)); QCOMPARE((QRect) dev.exactBounds(), QRect(0, 0, 1, 128)); } dev.clear(); { KisVLineIteratorSP it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); } dev.clear(); dev.setX(10); dev.setY(-15); { KisVLineIteratorSP it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); } } void KisIteratorTest::randomAccessor(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisRandomConstAccessorSP acc = dev.createRandomConstAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { acc->moveTo(x, y); } } - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisRandomAccessorSP ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); dev.clear(); dev.setX(10); dev.setY(-15); { KisRandomAccessorSP ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(-54, -15, 192, 192)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); } } void KisIteratorTest::repeatHLineIter(const KoColorSpace* cs) { KoColor color(Qt::green, cs); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(5, 5, 10, 10, color.data()); KisRepeatHLineConstIteratorSP iter = dev->createRepeatHLineConstIterator(0, 0, 20, QRect(5, 5, 10, 10)); for(int i = 0; i < 20; i++) { do { QVERIFY(!memcmp(color.data(), iter->oldRawData(), cs->pixelSize())); } while (iter->nextPixel()); iter->nextRow(); } } void KisIteratorTest::repeatVLineIter(const KoColorSpace* cs) { KoColor color(Qt::green, cs); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(5, 5, 10, 10, color.data()); KisRepeatVLineConstIteratorSP iter = dev->createRepeatVLineConstIterator(0, 0, 20, QRect(5, 5, 10, 10)); for(int i = 0; i < 20; i++) { do { QVERIFY(!memcmp(color.data(), iter->oldRawData(), cs->pixelSize())); } while (iter->nextPixel()); iter->nextColumn(); } } void KisIteratorTest::writeBytes() { allCsApplicator(&KisIteratorTest::writeBytes); } void KisIteratorTest::fill() { allCsApplicator(&KisIteratorTest::fill); } void KisIteratorTest::hLineIter() { allCsApplicator(&KisIteratorTest::hLineIter); } void KisIteratorTest::vLineIter() { allCsApplicator(&KisIteratorTest::vLineIter); } void KisIteratorTest::randomAccessor() { allCsApplicator(&KisIteratorTest::randomAccessor); } void KisIteratorTest::repeatHLineIter() { allCsApplicator(&KisIteratorTest::repeatHLineIter); } void KisIteratorTest::repeatVLineIter() { allCsApplicator(&KisIteratorTest::repeatVLineIter); } #define NUM_CYCLES 10000 #define NUM_THREADS 10 class DataReaderThread : public QRunnable { public: DataReaderThread(KisPaintDeviceSP device, const QRect &rect) : m_device(device), m_rect(rect) {} void run() override { for(int i = 0; i < NUM_CYCLES; i++) { KisRandomAccessorSP iter = m_device->createRandomAccessorNG(m_rect.x(), m_rect.y()); qint32 rowsRemaining = m_rect.height(); qint32 y = m_rect.y(); while (rowsRemaining > 0) { qint32 columnsRemaining = m_rect.width(); qint32 x = m_rect.x(); qint32 numContiguousRows = iter->numContiguousRows(y); qint32 rows = qMin(numContiguousRows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousColumns = iter->numContiguousColumns(x); qint32 columns = qMin(numContiguousColumns, columnsRemaining); qint32 rowStride = iter->rowStride(x, y); iter->moveTo(x, y); // dbgKrita << "BitBlt:" << ppVar(x) << ppVar(y) // << ppVar(columns) << ppVar(rows) // << ppVar(rowStride); doBitBlt(iter->rawData(), rowStride, m_device->pixelSize(), rows, columns); x += columns; columnsRemaining -= columns; } y += rows; rowsRemaining -= rows; } } } private: void doBitBltConst(const quint8* data, qint32 rowStride, qint32 pixelSize, qint32 rows, qint32 columns) { for(int i = 0; i < rows; i++) { Q_ASSERT(columns * pixelSize < 256); quint8 tempData[256]; memcpy(tempData, data, columns * pixelSize); data += rowStride; } } void doBitBlt(quint8* data, qint32 rowStride, qint32 pixelSize, qint32 rows, qint32 columns) { for(int i = 0; i < rows; i++) { // Let's write something here... memset(data, 0x13, columns * pixelSize); data += rowStride; } } private: KisPaintDeviceSP m_device; QRect m_rect; }; class NastyThread : public QRunnable { public: NastyThread(KisPaintDeviceSP device) : m_device(device) {} void run() override { for(int i = 0; i < NUM_CYCLES; i++) { m_device->setX(-0x400 + (qrand() & 0x7FF)); m_device->setY(-0x400 + (qrand() & 0x7FF)); QTest::qSleep(10); } } private: KisPaintDeviceSP m_device; }; void KisIteratorTest::stressTest() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QRect imageRect(0,0,2000,2000); KisPaintDeviceSP device = new KisPaintDevice(colorSpace); device->fill(imageRect, KoColor(Qt::red, colorSpace)); QThreadPool threadPool; threadPool.setMaxThreadCount(NUM_THREADS); for(int i = 0; i< NUM_THREADS; i++) { QRect rc = QRect(double(i) / NUM_THREADS * 2000, 0, 2000 / NUM_THREADS, 2000); // dbgKrita << rc; DataReaderThread *reader = new DataReaderThread(device, rc); threadPool.start(reader); if(!(i & 0x1)) { NastyThread *nasty = new NastyThread(device); threadPool.start(nasty); } } threadPool.waitForDone(); } -QTEST_MAIN(KisIteratorTest) +KISTEST_MAIN(KisIteratorTest) diff --git a/libs/image/tests/kis_iterators_ng_test.cpp b/libs/image/tests/kis_iterators_ng_test.cpp index 28e2818515..b6500fb3ae 100644 --- a/libs/image/tests/kis_iterators_ng_test.cpp +++ b/libs/image/tests/kis_iterators_ng_test.cpp @@ -1,497 +1,497 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_iterators_ng_test.h" #include #include #include #include #include #include #include "kis_random_accessor_ng.h" #include "kis_random_sub_accessor.h" #include "kis_paint_device.h" #include #include "kis_global.h" #include "testutil.h" -void KisIteratorTest::allCsApplicator(void (KisIteratorTest::* funcPtr)(const KoColorSpace*cs)) +void KisIteratorNGTest::allCsApplicator(void (KisIteratorNGTest::* funcPtr)(const KoColorSpace*cs)) { - QList colorsapces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); + QList colorspaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); - Q_FOREACH (const KoColorSpace* cs, colorsapces) { + Q_FOREACH (const KoColorSpace* cs, colorspaces) { dbgKrita << "Testing with" << cs->id(); if (cs->id() != "GRAYU16") // No point in testing extend for GRAYU16 (this->*funcPtr)(cs); } } inline quint8* allocatePixels(const KoColorSpace *colorSpace, int numPixels) { quint8 * bytes = new quint8[colorSpace->pixelSize() * 64 * 64 * 10]; KoColor color(Qt::red, colorSpace); const int pixelSize = colorSpace->pixelSize(); for(int i = 0; i < numPixels; i++) { memcpy(bytes + i * pixelSize, color.data(), pixelSize); } return bytes; } -void KisIteratorTest::writeBytes(const KoColorSpace * colorSpace) +void KisIteratorNGTest::writeBytes(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); // Check allocation on tile boundaries // Allocate memory for a 2 * 5 tiles grid QScopedArrayPointer bytes(allocatePixels(colorSpace, 64 * 64 * 10)); // Covers 5 x 2 tiles dev.writeBytes(bytes.data(), 0, 0, 5 * 64, 2 * 64); // Covers QCOMPARE(dev.extent(), QRect(0, 0, 64 * 5, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 64 * 5, 64 * 2)); dev.clear(); QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); dev.clear(); // Covers three by three tiles dev.writeBytes(bytes.data(), 10, 10, 130, 130); QCOMPARE(dev.extent(), QRect(0, 0, 64 * 3, 64 * 3)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 130, 130)); dev.clear(); // Covers 11 x 2 tiles dev.writeBytes(bytes.data(), -10, -10, 10 * 64, 64); QCOMPARE(dev.extent(), QRect(-64, -64, 64 * 11, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(-10, -10, 640, 64)); } -void KisIteratorTest::fill(const KoColorSpace * colorSpace) +void KisIteratorNGTest::fill(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); dev.fill(0, 0, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 5, 5)); dev.clear(); dev.fill(5, 5, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 5, 5)); dev.clear(); dev.fill(5, 5, 500, 500, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 8 * 64, 8 * 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 500, 500)); dev.clear(); dev.fill(33, -10, 348, 1028, bytes.data()); QCOMPARE(dev.extent(), QRect(0, -64, 6 * 64, 17 * 64)); QCOMPARE(dev.exactBounds(), QRect(33, -10, 348, 1028)); } -void KisIteratorTest::sequentialIter(const KoColorSpace * colorSpace) +void KisIteratorNGTest::sequentialIter(const KoColorSpace * colorSpace) { KisPaintDeviceSP dev = new KisPaintDevice(colorSpace); - QCOMPARE(dev->extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev->extent(), QRect(0, 0, 0, 0)); // Const does not extend the extent { KisSequentialConstIterator it(dev, QRect(0, 0, 128, 128)); while (it.nextPixel()); - QCOMPARE(dev->extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev->extent(), QRect(0, 0, 0, 0)); QCOMPARE(dev->exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); } // Non-const does { KisSequentialIterator it(dev, QRect(0, 0, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); memcpy(it.rawData(), c.data(), colorSpace->pixelSize()); QCOMPARE(it.x(), i % 128); QCOMPARE(it.y(), i / 128); } QCOMPARE(dev->extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev->exactBounds(), QRect(0, 0, 128, 128)); } { // check const iterator KisSequentialConstIterator it(dev, QRect(0, 0, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); QVERIFY(memcmp(it.rawDataConst(), c.data(), colorSpace->pixelSize()) == 0); } QCOMPARE(dev->extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev->exactBounds(), QRect(0, 0, 128, 128)); } { // check const iterator with **empty** area! It should neither crash nor enter the loop KisSequentialConstIterator it(dev, QRect()); QVERIFY(!it.rawDataConst()); QVERIFY(!it.oldRawData()); while (it.nextPixel()) { QVERIFY(0 && "we should never enter the loop"); } } { // check const iterator with strides KisSequentialConstIterator it(dev, QRect(0, 0, 128, 128)); int i = -1; int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { numConseqPixels = it.nConseqPixels(); for (int j = 0; j < numConseqPixels; j++) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); QVERIFY(memcmp(it.rawDataConst() + j * colorSpace->pixelSize(), c.data(), colorSpace->pixelSize()) == 0); } } QCOMPARE(dev->extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev->exactBounds(), QRect(0, 0, 128, 128)); } { // check const iterator with strides and **empty** area KisSequentialConstIterator it(dev, QRect()); QVERIFY(!it.rawDataConst()); QVERIFY(!it.oldRawData()); int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { QVERIFY(0 && "we should never enter the loop"); } } dev->clear(); { KisSequentialIterator it(dev, QRect(10, 10, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); memcpy(it.rawData(), c.data(), colorSpace->pixelSize()); } QCOMPARE(dev->extent(), QRect(0, 0, 3 * 64, 3 * 64)); QCOMPARE(dev->exactBounds(), QRect(10, 10, 128, 128)); } dev->clear(); dev->setX(10); dev->setY(-15); { KisSequentialIterator it(dev, QRect(10, 10, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); memcpy(it.rawData(), c.data(), colorSpace->pixelSize()); } QCOMPARE(dev->extent(), QRect(10, -15, 128, 192)); QCOMPARE(dev->exactBounds(), QRect(10, 10, 128, 128)); } { KisSequentialIterator it(dev, QRect(10, 10, 128, 128)); QCOMPARE(it.rawData(), it.oldRawData()); } } -void KisIteratorTest::hLineIter(const KoColorSpace * colorSpace) +void KisIteratorNGTest::hLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisHLineConstIteratorSP cit = dev.createHLineConstIteratorNG(0, 0, 128); while (!cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); dev.clear(); KisHLineIteratorSP it = dev.createHLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 1)); dev.clear(); it = dev.createHLineIteratorNG(0, 1, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 1, 128, 1)); dev.clear(); it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 192, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); dev.clear(); dev.setX(10); dev.setY(-15); it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); it = dev.createHLineIteratorNG(10, 10, 128); it->nextRow(); QCOMPARE(it->rawData(), it->oldRawData()); } -void KisIteratorTest::justCreation(const KoColorSpace * colorSpace) +void KisIteratorNGTest::justCreation(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); dev.createVLineConstIteratorNG(0, 0, 128); dev.createVLineIteratorNG(0, 0, 128); dev.createHLineConstIteratorNG(0, 0, 128); dev.createHLineIteratorNG(0, 0, 128); } -void KisIteratorTest::vLineIter(const KoColorSpace * colorSpace) +void KisIteratorNGTest::vLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisVLineConstIteratorSP cit = dev.createVLineConstIteratorNG(0, 0, 128); while (cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); cit.clear(); KisVLineIteratorSP it = dev.createVLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while(it->nextPixel()); QCOMPARE((QRect) dev.extent(), QRect(0, 0, 64, 128)); QCOMPARE((QRect) dev.exactBounds(), QRect(0, 0, 1, 128)); it.clear(); dev.clear(); it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while(it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); dev.clear(); dev.setX(10); dev.setY(-15); it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while(it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); it = dev.createVLineIteratorNG(10, 10, 128); it->nextColumn(); QCOMPARE(it->rawData(), it->oldRawData()); } -void KisIteratorTest::randomAccessor(const KoColorSpace * colorSpace) +void KisIteratorNGTest::randomAccessor(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisRandomConstAccessorSP acc = dev.createRandomConstAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { acc->moveTo(x, y); } } - QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); + QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); KisRandomAccessorSP ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); dev.clear(); dev.setX(10); dev.setY(-15); ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(-54, -15, 192, 192)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); } -void KisIteratorTest::writeBytes() +void KisIteratorNGTest::writeBytes() { - allCsApplicator(&KisIteratorTest::writeBytes); + allCsApplicator(&KisIteratorNGTest::writeBytes); } -void KisIteratorTest::fill() +void KisIteratorNGTest::fill() { - allCsApplicator(&KisIteratorTest::fill); + allCsApplicator(&KisIteratorNGTest::fill); } -void KisIteratorTest::sequentialIter() +void KisIteratorNGTest::sequentialIter() { - allCsApplicator(&KisIteratorTest::sequentialIter); + allCsApplicator(&KisIteratorNGTest::sequentialIter); } #include -void KisIteratorTest::sequentialIteratorWithProgress() +void KisIteratorNGTest::sequentialIteratorWithProgress() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect rc(10,10,200,200); TestUtil::TestProgressBar proxy; KisSequentialConstIteratorProgress it (dev, rc, &proxy); while (it.nextPixel()) { QCOMPARE(proxy.min(), rc.top()); QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), it.y()); } QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), proxy.max()); } -void KisIteratorTest::sequentialIteratorWithProgressIncomplete() +void KisIteratorNGTest::sequentialIteratorWithProgressIncomplete() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect rc(10,10,100,100); TestUtil::TestProgressBar proxy; { KisSequentialConstIteratorProgress it (dev, rc, &proxy); QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), rc.top()); } // on desruction, iterator automatically completes progress reporting QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), proxy.max()); } -void KisIteratorTest::hLineIter() +void KisIteratorNGTest::hLineIter() { - allCsApplicator(&KisIteratorTest::hLineIter); + allCsApplicator(&KisIteratorNGTest::hLineIter); } -void KisIteratorTest::justCreation() +void KisIteratorNGTest::justCreation() { - allCsApplicator(&KisIteratorTest::justCreation); + allCsApplicator(&KisIteratorNGTest::justCreation); } -void KisIteratorTest::vLineIter() +void KisIteratorNGTest::vLineIter() { - allCsApplicator(&KisIteratorTest::vLineIter); + allCsApplicator(&KisIteratorNGTest::vLineIter); } -void KisIteratorTest::randomAccessor() +void KisIteratorNGTest::randomAccessor() { - allCsApplicator(&KisIteratorTest::randomAccessor); + allCsApplicator(&KisIteratorNGTest::randomAccessor); } -QTEST_MAIN(KisIteratorTest) +KISTEST_MAIN(KisIteratorNGTest) diff --git a/libs/image/tests/kis_iterators_ng_test.h b/libs/image/tests/kis_iterators_ng_test.h index cc0cd7e42f..a3862bd83d 100644 --- a/libs/image/tests/kis_iterators_ng_test.h +++ b/libs/image/tests/kis_iterators_ng_test.h @@ -1,55 +1,55 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ITERATOR_NG_TEST_H #define KIS_ITERATOR_NG_TEST_H #include class KoColorSpace; -class KisIteratorTest : public QObject +class KisIteratorNGTest : public QObject { Q_OBJECT private: - void allCsApplicator(void (KisIteratorTest::* funcPtr)(const KoColorSpace*cs)); + void allCsApplicator(void (KisIteratorNGTest::* funcPtr)(const KoColorSpace*cs)); void justCreation(const KoColorSpace * cs); void vLineIter(const KoColorSpace * cs); void writeBytes(const KoColorSpace * cs); void fill(const KoColorSpace * cs); void sequentialIter(const KoColorSpace * colorSpace); void hLineIter(const KoColorSpace * cs); void randomAccessor(const KoColorSpace * cs); private Q_SLOTS: void justCreation(); void vLineIter(); void writeBytes(); void fill(); void sequentialIter(); void sequentialIteratorWithProgress(); void sequentialIteratorWithProgressIncomplete(); void hLineIter(); void randomAccessor(); }; #endif diff --git a/libs/image/tests/kis_meta_data_test.cpp b/libs/image/tests/kis_meta_data_test.cpp index aad524c858..9295c6404b 100644 --- a/libs/image/tests/kis_meta_data_test.cpp +++ b/libs/image/tests/kis_meta_data_test.cpp @@ -1,586 +1,592 @@ /* * Copyright (c) 2008-2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_test.h" #include #include "kis_meta_data_entry.h" #include "kis_meta_data_filter_registry.h" #include "kis_meta_data_value.h" #include "kis_meta_data_schema.h" #include "kis_meta_data_schema_registry.h" #include "kis_meta_data_store.h" #include "kis_meta_data_type_info.h" #include "kis_meta_data_type_info_p.h" #include "kis_meta_data_parser.h" #include "kis_meta_data_validator.h" +#include "sdk/tests/kistest.h" + using namespace KisMetaData; KisMetaData::Value KisMetaDataTest::createRationalValue() { return KisMetaData::Value(Rational(12, -42)); } KisMetaData::Value KisMetaDataTest::createIntegerValue(int v) { return KisMetaData::Value(v); } KisMetaData::Value KisMetaDataTest::createStringValue() { return KisMetaData::Value("Hello World !"); } KisMetaData::Value KisMetaDataTest::createListValue() { QList list; list << createRationalValue() << createIntegerValue() << createStringValue(); return list; } + +#define TEST_SCHEMA(uriStr) \ + { \ + const Schema* schema = SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::uriStr); \ + QVERIFY(schema); \ + QCOMPARE(schema->uri(), KisMetaData::Schema::uriStr); \ + QCOMPARE(schema, SchemaRegistry::instance()->schemaFromPrefix(schema->prefix()) ); \ + QVERIFY( !SchemaRegistry::instance()->create("http://tartampion.com", schema->prefix())); \ + QCOMPARE(schema, SchemaRegistry::instance()->create(KisMetaData::Schema::uriStr, "tartampion")); \ + QCOMPARE(QString(schema->prefix() + ":hello"), schema->generateQualifiedName("hello")); \ + } + + +void KisMetaDataTest::testSchemaBasic() +{ + TEST_SCHEMA(TIFFSchemaUri); + TEST_SCHEMA(EXIFSchemaUri); + TEST_SCHEMA(DublinCoreSchemaUri); + TEST_SCHEMA(XMPSchemaUri); + TEST_SCHEMA(XMPRightsSchemaUri); + TEST_SCHEMA(MakerNoteSchemaUri); + TEST_SCHEMA(IPTCSchemaUri); + TEST_SCHEMA(PhotoshopSchemaUri); +} + + + void KisMetaDataTest::testRationals() { { KisMetaData::Rational sr(-10, -14); QCOMPARE(sr.numerator, -10); QCOMPARE(sr.denominator, -14); KisMetaData::Rational sr2(14, 10); QVERIFY(sr == sr); QVERIFY(!(sr == sr2)); QVERIFY(sr != sr2); } { KisMetaData::Rational sr(10, 14); QCOMPARE(sr.numerator, 10); QCOMPARE(sr.denominator, 14); KisMetaData::Rational sr2(14, 10); QVERIFY(sr == sr); QVERIFY(!(sr == sr2)); QVERIFY(sr != sr2); } } void KisMetaDataTest::testValueCreation() { { Value v; QCOMPARE(v.type(), Value::Invalid); } { Value v(10); QCOMPARE(v.type(), Value::Variant); QCOMPARE(v.asVariant().toInt(), 10); QCOMPARE(v.asInteger(), 10); QCOMPARE(createIntegerValue().type(), Value::Variant); } { Value v("Hello World !"); QCOMPARE(v.type(), Value::Variant); QCOMPARE(v.asVariant().toString(), QString("Hello World !")); QCOMPARE(createStringValue().type(), Value::Variant); } { KisMetaData::Rational sr(42, -12); Value v(sr); QCOMPARE(v.type(), Value::Rational); QCOMPARE(v.asRational(), sr); QCOMPARE(createRationalValue().type(), Value::Rational); QCOMPARE(v.asInteger(), -42 / 12); QCOMPARE(v.asDouble(), -42.0 / 12.0); } { KisMetaData::Rational sr(42, 12); Value v(sr); QCOMPARE(v.type(), Value::Rational); QCOMPARE(v.asRational(), sr); QCOMPARE(createRationalValue().type(), Value::Rational); QCOMPARE(v.asInteger(), 42 / 12); QCOMPARE(v.asDouble(), 42.0 / 12.0); } { QList list; list << createRationalValue() << createIntegerValue() << createStringValue(); Value v(list); QCOMPARE(v.type(), Value::OrderedArray); QVERIFY(v.isArray()); QCOMPARE(v.asArray(), list); QCOMPARE(createListValue().type(), Value::OrderedArray); } { Value v(QList(), Value::OrderedArray); QCOMPARE(v.type(), Value::OrderedArray); QVERIFY(v.isArray()); } { Value v(QList(), Value::UnorderedArray); QCOMPARE(v.type(), Value::UnorderedArray); QVERIFY(v.isArray()); } { Value v(QList(), Value::AlternativeArray); QCOMPARE(v.type(), Value::AlternativeArray); QVERIFY(v.isArray()); } } void KisMetaDataTest::testValueEquality() { QVERIFY(createRationalValue() == createRationalValue()); QVERIFY(createIntegerValue() == createIntegerValue()); QVERIFY(createStringValue() == createStringValue()); QVERIFY(createListValue() == createListValue()); } #define TEST_VALUE_COPY(func) \ { \ Value v1 = func(); \ Value v2(v1); \ Value v3 = v1; \ QCOMPARE(v1, v2); \ QCOMPARE(v1, v3); \ } void KisMetaDataTest::testValueCopy() { TEST_VALUE_COPY(createRationalValue); TEST_VALUE_COPY(createIntegerValue); TEST_VALUE_COPY(createStringValue); TEST_VALUE_COPY(createListValue); } - -#define TEST_SCHEMA(uriStr) \ - { \ - const Schema* schema = SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::uriStr); \ - QVERIFY(schema); \ - QCOMPARE(schema->uri(), KisMetaData::Schema::uriStr); \ - QCOMPARE(schema, SchemaRegistry::instance()->schemaFromPrefix(schema->prefix()) ); \ - QVERIFY( !SchemaRegistry::instance()->create("http://tartampion.com", schema->prefix())); \ - QCOMPARE(schema, SchemaRegistry::instance()->create(KisMetaData::Schema::uriStr, "tartampion")); \ - QCOMPARE(QString(schema->prefix() + ":hello"), schema->generateQualifiedName("hello")); \ - } - - -void KisMetaDataTest::testSchemaBasic() -{ - TEST_SCHEMA(TIFFSchemaUri); - TEST_SCHEMA(EXIFSchemaUri); - TEST_SCHEMA(DublinCoreSchemaUri); - TEST_SCHEMA(XMPSchemaUri); - TEST_SCHEMA(XMPRightsSchemaUri); - TEST_SCHEMA(MakerNoteSchemaUri); - TEST_SCHEMA(IPTCSchemaUri); - TEST_SCHEMA(PhotoshopSchemaUri); -} - void KisMetaDataTest::testEntry() { const Schema* schema = SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri); Value v1 = createIntegerValue(42); Value v2 = createIntegerValue(12); Entry e(schema, "test", v1); QVERIFY(schema); QCOMPARE(e.name(), QString("test")); QCOMPARE(e.schema(), schema); QCOMPARE(e.qualifiedName(), schema->generateQualifiedName("test")); QCOMPARE(e.value(), v1); e.value() = v2; QCOMPARE(e.value(), v2); } void KisMetaDataTest::testStore() { const Schema* schema = SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri); + QVERIFY(schema); Store s; Entry e(schema, "test", createIntegerValue()); QVERIFY(!s.containsEntry(schema, "test")); s.addEntry(e); QVERIFY(s.containsEntry(schema, "test")); QVERIFY(s.containsEntry(e.qualifiedName())); QVERIFY(s.containsEntry(KisMetaData::Schema::TIFFSchemaUri, "test")); s.removeEntry(schema, "test"); QVERIFY(!s.containsEntry(schema, "test")); Entry& e2 = s.getEntry(schema, "hello"); QVERIFY(s.containsEntry(schema, "hello")); QVERIFY(e2.name() == "hello"); QVERIFY(e2.schema() == schema); } void KisMetaDataTest::testFilters() { // Test anonymizer { Store s; const KisMetaData::Filter* filter = FilterRegistry::instance()->get("Anonymizer"); QVERIFY(filter); const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); + QVERIFY(dcSchema); s.addEntry(Entry(dcSchema, "contributor", Value("somevalue"))); s.addEntry(Entry(dcSchema, "creator", Value("somevalue"))); s.addEntry(Entry(dcSchema, "publisher", Value("somevalue"))); s.addEntry(Entry(dcSchema, "rights", Value("somevalue"))); const KisMetaData::Schema* psSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::PhotoshopSchemaUri); s.addEntry(Entry(psSchema, "AuthorsPosition", Value("somevalue"))); s.addEntry(Entry(psSchema, "CaptionWriter", Value("somevalue"))); s.addEntry(Entry(psSchema, "Credit", Value("somevalue"))); s.addEntry(Entry(psSchema, "City", Value("somevalue"))); s.addEntry(Entry(psSchema, "Country", Value("somevalue"))); QList filters; filters << filter; s.applyFilters(filters); QVERIFY(!s.containsEntry(dcSchema, "contributor")); QVERIFY(!s.containsEntry(dcSchema, "creator")); QVERIFY(!s.containsEntry(dcSchema, "publisher")); QVERIFY(!s.containsEntry(dcSchema, "rights")); QVERIFY(!s.containsEntry(psSchema, "AuthorsPosition")); QVERIFY(!s.containsEntry(psSchema, "CaptionWriter")); QVERIFY(!s.containsEntry(psSchema, "Credit")); QVERIFY(!s.containsEntry(psSchema, "City")); QVERIFY(!s.containsEntry(psSchema, "Country")); } } void KisMetaDataTest::testTypeInfo() { QVERIFY(TypeInfo::Private::Boolean->propertyType() == TypeInfo::BooleanType); QVERIFY(TypeInfo::Private::Boolean->embeddedPropertyType() == 0); QVERIFY(TypeInfo::Private::Boolean->choices().size() == 0); QVERIFY(TypeInfo::Private::Boolean->hasCorrectType(Value(true))); QVERIFY(!TypeInfo::Private::Boolean->hasCorrectType(createIntegerValue())); QVERIFY(!TypeInfo::Private::Boolean->hasCorrectType(createStringValue())); QVERIFY(!TypeInfo::Private::Boolean->hasCorrectType(createListValue())); QVERIFY(TypeInfo::Private::Integer->propertyType() == TypeInfo::IntegerType); QVERIFY(TypeInfo::Private::Integer->embeddedPropertyType() == 0); QVERIFY(TypeInfo::Private::Integer->choices().size() == 0); QVERIFY(!TypeInfo::Private::Integer->hasCorrectType(Value(true))); QVERIFY(TypeInfo::Private::Integer->hasCorrectType(createIntegerValue())); QVERIFY(!TypeInfo::Private::Integer->hasCorrectType(createStringValue())); QVERIFY(!TypeInfo::Private::Integer->hasCorrectType(createListValue())); QVERIFY(TypeInfo::Private::Text->propertyType() == TypeInfo::TextType); QVERIFY(TypeInfo::Private::Text->embeddedPropertyType() == 0); QVERIFY(TypeInfo::Private::Text->choices().size() == 0); QVERIFY(!TypeInfo::Private::Text->hasCorrectType(Value(true))); QVERIFY(!TypeInfo::Private::Text->hasCorrectType(createIntegerValue())); QVERIFY(TypeInfo::Private::Text->hasCorrectType(createStringValue())); QVERIFY(!TypeInfo::Private::Text->hasCorrectType(createListValue())); QVERIFY(TypeInfo::Private::Date->propertyType() == TypeInfo::DateType); QVERIFY(TypeInfo::Private::Date->embeddedPropertyType() == 0); QVERIFY(TypeInfo::Private::Date->choices().size() == 0); QVERIFY(TypeInfo::Private::Date->hasCorrectType(Value(QDateTime()))); QVERIFY(!TypeInfo::Private::Date->hasCorrectType(createIntegerValue())); QVERIFY(!TypeInfo::Private::Date->hasCorrectType(createStringValue())); QVERIFY(!TypeInfo::Private::Date->hasCorrectType(createListValue())); QVERIFY(TypeInfo::Private::Rational->propertyType() == TypeInfo::RationalType); QVERIFY(TypeInfo::Private::Rational->embeddedPropertyType() == 0); QVERIFY(TypeInfo::Private::Rational->choices().size() == 0); QVERIFY(TypeInfo::Private::Rational->hasCorrectType(createRationalValue())); QVERIFY(!TypeInfo::Private::Rational->hasCorrectType(createIntegerValue())); QVERIFY(!TypeInfo::Private::Rational->hasCorrectType(createStringValue())); QVERIFY(!TypeInfo::Private::Rational->hasCorrectType(createListValue())); QVERIFY(TypeInfo::Private::GPSCoordinate->propertyType() == TypeInfo::GPSCoordinateType); QVERIFY(TypeInfo::Private::GPSCoordinate->embeddedPropertyType() == 0); QVERIFY(TypeInfo::Private::GPSCoordinate->choices().size() == 0); QVERIFY(TypeInfo::Private::LangArray->propertyType() == TypeInfo::LangArrayType); QVERIFY(TypeInfo::Private::LangArray->embeddedPropertyType() == TypeInfo::Private::Text); QVERIFY(TypeInfo::Private::LangArray->choices().size() == 0); // Create List of integer Value QList< Value > goodIntegerList; goodIntegerList.push_back(createIntegerValue()); goodIntegerList.push_back(createIntegerValue(12)); QList< Value > badIntegerList; badIntegerList.push_back(createIntegerValue()); badIntegerList.push_back(createStringValue()); badIntegerList.push_back(createIntegerValue(12)); // Test OrderedArray const TypeInfo* arrOA1 = TypeInfo::Private::orderedArray(TypeInfo::Private::Integer); QVERIFY(arrOA1->propertyType() == TypeInfo::OrderedArrayType); QVERIFY(arrOA1->embeddedPropertyType() == TypeInfo::Private::Integer); QVERIFY(arrOA1->choices().size() == 0); QVERIFY(arrOA1 == TypeInfo::Private::orderedArray(TypeInfo::Private::Integer)); const TypeInfo* arrOA2 = TypeInfo::Private::orderedArray(TypeInfo::Private::Text); QVERIFY(arrOA1 != arrOA2); QVERIFY(arrOA2->embeddedPropertyType() == TypeInfo::Private::Text); QVERIFY(!arrOA1->hasCorrectType(Value(true))); QVERIFY(!arrOA1->hasCorrectType(createIntegerValue())); QVERIFY(!arrOA1->hasCorrectType(createStringValue())); QVERIFY(!arrOA1->hasCorrectType(createListValue())); QVERIFY(arrOA1->hasCorrectType(Value(goodIntegerList, Value::OrderedArray))); QVERIFY(!arrOA1->hasCorrectType(Value(badIntegerList, Value::OrderedArray))); // Test UnarderedArray const TypeInfo* arrUOA1 = TypeInfo::Private::unorderedArray(TypeInfo::Private::Integer); QVERIFY(arrUOA1->propertyType() == TypeInfo::UnorderedArrayType); QVERIFY(arrUOA1->embeddedPropertyType() == TypeInfo::Private::Integer); QVERIFY(arrUOA1->choices().size() == 0); QVERIFY(arrUOA1 == TypeInfo::Private::unorderedArray(TypeInfo::Private::Integer)); const TypeInfo* arrUOA2 = TypeInfo::Private::unorderedArray(TypeInfo::Private::Text); QVERIFY(arrUOA1 != arrUOA2); QVERIFY(arrUOA2->embeddedPropertyType() == TypeInfo::Private::Text); QVERIFY(arrUOA1 != arrOA1); QVERIFY(arrUOA2 != arrOA2); QVERIFY(!arrUOA1->hasCorrectType(Value(true))); QVERIFY(!arrUOA1->hasCorrectType(createIntegerValue())); QVERIFY(!arrUOA1->hasCorrectType(createStringValue())); QVERIFY(!arrUOA1->hasCorrectType(createListValue())); QVERIFY(arrUOA1->hasCorrectType(Value(goodIntegerList, Value::UnorderedArray))); QVERIFY(!arrUOA1->hasCorrectType(Value(badIntegerList, Value::UnorderedArray))); // Test AlternativeArray const TypeInfo* arrAA1 = TypeInfo::Private::alternativeArray(TypeInfo::Private::Integer); QVERIFY(arrAA1->propertyType() == TypeInfo::AlternativeArrayType); QVERIFY(arrAA1->embeddedPropertyType() == TypeInfo::Private::Integer); QVERIFY(arrAA1->choices().size() == 0); QVERIFY(arrAA1 == TypeInfo::Private::alternativeArray(TypeInfo::Private::Integer)); const TypeInfo* arrAA2 = TypeInfo::Private::alternativeArray(TypeInfo::Private::Text); QVERIFY(arrAA1 != arrAA2); QVERIFY(arrAA2->embeddedPropertyType() == TypeInfo::Private::Text); QVERIFY(arrAA1 != arrOA1); QVERIFY(arrAA1 != arrUOA1); QVERIFY(arrAA2 != arrOA2); QVERIFY(arrAA2 != arrUOA2); QVERIFY(!arrAA1->hasCorrectType(Value(true))); QVERIFY(!arrAA1->hasCorrectType(createIntegerValue())); QVERIFY(!arrAA1->hasCorrectType(createStringValue())); QVERIFY(!arrAA1->hasCorrectType(createListValue())); QVERIFY(arrAA1->hasCorrectType(Value(goodIntegerList, Value::AlternativeArray))); QVERIFY(!arrAA1->hasCorrectType(Value(badIntegerList, Value::AlternativeArray))); // Test Choice QList< TypeInfo::Choice > choices; choices.push_back(TypeInfo::Choice(Value(12), "Hello")); choices.push_back(TypeInfo::Choice(Value(42), "World")); const TypeInfo* oChoice = TypeInfo::Private::createChoice(TypeInfo::OpenedChoice, TypeInfo::Private::Integer, choices); QVERIFY(oChoice->propertyType() == TypeInfo::OpenedChoice); QVERIFY(oChoice->embeddedPropertyType() == TypeInfo::Private::Integer); QVERIFY(oChoice->choices().size() == 2); QVERIFY(oChoice->choices()[0].value() == Value(12)); QVERIFY(oChoice->choices()[0].hint() == "Hello"); QVERIFY(oChoice->choices()[1].value() == Value(42)); QVERIFY(oChoice->choices()[1].hint() == "World"); QVERIFY(!oChoice->hasCorrectType(Value(true))); QVERIFY(oChoice->hasCorrectType(createIntegerValue(12))); QVERIFY(oChoice->hasCorrectType(createIntegerValue(-12))); QVERIFY(oChoice->hasCorrectType(createIntegerValue(42))); QVERIFY(!oChoice->hasCorrectType(createStringValue())); QVERIFY(!oChoice->hasCorrectType(createListValue())); QVERIFY(!oChoice->hasCorrectType(Value(goodIntegerList, Value::AlternativeArray))); QVERIFY(!oChoice->hasCorrectType(Value(badIntegerList, Value::AlternativeArray))); const TypeInfo* cChoice = TypeInfo::Private::createChoice(TypeInfo::ClosedChoice, TypeInfo::Private::Integer, choices); QVERIFY(cChoice->propertyType() == TypeInfo::ClosedChoice); QVERIFY(!cChoice->hasCorrectType(Value(true))); QVERIFY(cChoice->hasCorrectType(createIntegerValue(12))); QVERIFY(cChoice->hasCorrectType(createIntegerValue(-12))); QVERIFY(cChoice->hasCorrectType(createIntegerValue(42))); QVERIFY(!cChoice->hasCorrectType(createStringValue())); QVERIFY(!cChoice->hasCorrectType(createListValue())); QVERIFY(!cChoice->hasCorrectType(Value(goodIntegerList, Value::AlternativeArray))); QVERIFY(!cChoice->hasCorrectType(Value(badIntegerList, Value::AlternativeArray))); QVERIFY(cChoice->hasCorrectValue(createIntegerValue(12))); QVERIFY(!cChoice->hasCorrectValue(createIntegerValue(-12))); QVERIFY(cChoice->hasCorrectValue(createIntegerValue(42))); // Test structure } void KisMetaDataTest::testSchemaParse() { const Schema* exifSchema = SchemaRegistry::instance()->schemaFromUri(Schema::EXIFSchemaUri); QVERIFY(exifSchema); const TypeInfo* colorSpaceType = exifSchema->propertyType("ColorSpace"); QVERIFY(colorSpaceType); QVERIFY(colorSpaceType->propertyType() == TypeInfo::ClosedChoice); QVERIFY(colorSpaceType->choices().size() == 2); QVERIFY(colorSpaceType->choices()[0].value() == Value(1)); QVERIFY(colorSpaceType->choices()[0].hint() == "sRGB"); QVERIFY(colorSpaceType->choices()[1].value() == Value(65635)); QVERIFY(colorSpaceType->choices()[1].hint() == "uncalibrated"); QVERIFY(exifSchema->propertyType("CompressedBitsPerPixel")); QVERIFY(exifSchema->propertyType("CompressedBitsPerPixel")->propertyType() == TypeInfo::RationalType); QVERIFY(exifSchema->propertyType("PixelXDimension")); QVERIFY(exifSchema->propertyType("PixelXDimension")->propertyType() == TypeInfo::IntegerType); QVERIFY(exifSchema->propertyType("UserComment")); QVERIFY(exifSchema->propertyType("UserComment")->propertyType() == TypeInfo::LangArrayType); QVERIFY(exifSchema->propertyType("RelatedSoundFile")); QVERIFY(exifSchema->propertyType("RelatedSoundFile")->propertyType() == TypeInfo::TextType); QVERIFY(exifSchema->propertyType("DateTimeOriginal")); QVERIFY(exifSchema->propertyType("DateTimeOriginal")->propertyType() == TypeInfo::DateType); QVERIFY(exifSchema->propertyType("ISOSpeedRatings")); QVERIFY(exifSchema->propertyType("ISOSpeedRatings")->propertyType() == TypeInfo::OrderedArrayType); QVERIFY(exifSchema->propertyType("ISOSpeedRatings")->embeddedPropertyType() == TypeInfo::Private::Integer); const TypeInfo* oecfType = exifSchema->propertyType("OECF"); QVERIFY(oecfType); QVERIFY(oecfType->propertyType() == TypeInfo::StructureType); QVERIFY(oecfType == exifSchema->structure("OECFSFR")); QVERIFY(oecfType->structureName() == "OECFSFR"); QVERIFY(oecfType->structureSchema()); QVERIFY(oecfType->structureSchema()->propertyType("Columns")->propertyType() == TypeInfo::IntegerType); QVERIFY(oecfType->structureSchema()->propertyType("Rows")->propertyType() == TypeInfo::IntegerType); QVERIFY(oecfType->structureSchema()->propertyType("Names")->propertyType() == TypeInfo::OrderedArrayType); QVERIFY(oecfType->structureSchema()->propertyType("Names")->embeddedPropertyType()->propertyType() == TypeInfo::TextType); QVERIFY(oecfType->structureSchema()->propertyType("Values")->propertyType() == TypeInfo::OrderedArrayType); QVERIFY(oecfType->structureSchema()->propertyType("Values")->embeddedPropertyType()->propertyType() == TypeInfo::RationalType); } void KisMetaDataTest::testParser() { Value intV = TypeInfo::Private::Integer->parser()->parse("1242"); QVERIFY(intV.type() == Value::Variant); QVERIFY(intV.asVariant() == 1242); QVERIFY(intV.asVariant().type() == QVariant::Int); Value textV = TypeInfo::Private::Text->parser()->parse("Bouh"); QVERIFY(textV.type() == Value::Variant); QVERIFY(textV.asVariant() == "Bouh"); QVERIFY(textV.asVariant().type() == QVariant::String); Value dateV1 = TypeInfo::Private::Date->parser()->parse("2005-10-31"); QVERIFY(dateV1.type() == Value::Variant); QDateTime d1 = dateV1.asVariant().toDateTime(); QVERIFY(d1.date().year() == 2005); QVERIFY(d1.date().month() == 10); QVERIFY(d1.date().day() == 31); Value dateV2 = TypeInfo::Private::Date->parser()->parse("2005"); QVERIFY(dateV2.type() == Value::Variant); QDateTime d2 = dateV2.asVariant().toDateTime(); QVERIFY(d2.date().year() == 2005); Value dateV3 = TypeInfo::Private::Date->parser()->parse("2005-12"); QVERIFY(dateV3.type() == Value::Variant); QDateTime d3 = dateV3.asVariant().toDateTime(); QVERIFY(d3.date().year() == 2005); QVERIFY(d3.date().month() == 12); Value dateV4 = TypeInfo::Private::Date->parser()->parse("2005-10-31T12:20"); QVERIFY(dateV4.type() == Value::Variant); QDateTime d4 = dateV4.asVariant().toDateTime(); QVERIFY(d4.date().year() == 2005); QVERIFY(d4.date().month() == 10); QVERIFY(d4.date().day() == 31); QVERIFY(d4.time().hour() == 12); QVERIFY(d4.time().minute() == 20); Value dateV5 = TypeInfo::Private::Date->parser()->parse("2005-10-31T12:20:32"); QVERIFY(dateV5.type() == Value::Variant); QDateTime d5 = dateV5.asVariant().toDateTime(); QVERIFY(d5.date().year() == 2005); QVERIFY(d5.date().month() == 10); QVERIFY(d5.date().day() == 31); QVERIFY(d5.time().hour() == 12); QVERIFY(d5.time().minute() == 20); QVERIFY(d5.time().second() == 32); Value dateV6 = TypeInfo::Private::Date->parser()->parse("2005-10-31T12:20:32-06:00"); QVERIFY(dateV6.type() == Value::Variant); QDateTime d6 = dateV6.asVariant().toDateTime(); QVERIFY(d6.date().year() == 2005); QVERIFY(d6.date().month() == 10); QVERIFY(d6.date().day() == 31); QVERIFY(d6.time().hour() == 18); QVERIFY(d6.time().minute() == 20); QVERIFY(d6.time().second() == 32); Value rational1 = TypeInfo::Private::Rational->parser()->parse("-10/20"); QVERIFY(rational1.type() == Value::Rational); QVERIFY(rational1.asRational().numerator == -10); QVERIFY(rational1.asRational().denominator == 20); Value rational2 = TypeInfo::Private::Rational->parser()->parse("10/20"); QVERIFY(rational2.type() == Value::Rational); QVERIFY(rational2.asRational().numerator == 10); QVERIFY(rational2.asRational().denominator == 20); } void KisMetaDataTest::testValidator() { Store store; const Schema* exif = SchemaRegistry::instance()->schemaFromUri(Schema::EXIFSchemaUri); QVERIFY(exif); store.addEntry(Entry(exif, "PixelXDimension", createIntegerValue())); store.addEntry(Entry(exif, "PixelYDimension", createIntegerValue())); store.addEntry(Entry(exif, "RelatedSoundFile", createStringValue())); store.addEntry(Entry(exif, "ColorSpace", createIntegerValue(1))); store.addEntry(Entry(exif, "ExposureTime", createRationalValue())); Validator validator(&store); QCOMPARE(validator.countInvalidEntries(), 0); QCOMPARE(validator.countValidEntries(), 5); QCOMPARE(validator.invalidEntries().size(), 0); // Unknown entry store.addEntry(Entry(exif, "azerty", createIntegerValue())); validator.revalidate(); QCOMPARE(validator.countInvalidEntries(), 1); QCOMPARE(validator.countValidEntries(), 5); QCOMPARE(validator.invalidEntries()[ exif->generateQualifiedName("azerty")].type(), Validator::Reason::UNKNOWN_ENTRY); store.removeEntry(exif, "azerty"); // Invalid type for rational store.addEntry(Entry(exif, "FNumber", createIntegerValue())); validator.revalidate(); QCOMPARE(validator.countInvalidEntries(), 1); QCOMPARE(validator.countValidEntries(), 5); QCOMPARE(validator.invalidEntries()[ exif->generateQualifiedName("FNumber")].type(), Validator::Reason::INVALID_TYPE); store.removeEntry(exif, "FNumber"); // Invalid type for integer store.addEntry(Entry(exif, "SubjectLocation", createStringValue())); validator.revalidate(); QCOMPARE(validator.countInvalidEntries(), 1); QCOMPARE(validator.countValidEntries(), 5); QCOMPARE(validator.invalidEntries()[ exif->generateQualifiedName("SubjectLocation")].type(), Validator::Reason::INVALID_TYPE); store.removeEntry(exif, "SubjectLocation"); // Invalid type for choice store.addEntry(Entry(exif, "SensingMethod", createStringValue())); validator.revalidate(); QCOMPARE(validator.countInvalidEntries(), 1); QCOMPARE(validator.countValidEntries(), 5); QCOMPARE(validator.invalidEntries()[ exif->generateQualifiedName("SensingMethod")].type(), Validator::Reason::INVALID_TYPE); store.removeEntry(exif, "SensingMethod"); // Invalid value for choice store.addEntry(Entry(exif, "SensingMethod", createIntegerValue(1242))); validator.revalidate(); QCOMPARE(validator.countInvalidEntries(), 1); QCOMPARE(validator.countValidEntries(), 5); QCOMPARE(validator.invalidEntries()[ exif->generateQualifiedName("SensingMethod")].type(), Validator::Reason::INVALID_VALUE); store.removeEntry(exif, "SensingMethod"); } -QTEST_MAIN(KisMetaDataTest) +KISTEST_MAIN(KisMetaDataTest) diff --git a/libs/image/tests/kis_meta_data_test.h b/libs/image/tests/kis_meta_data_test.h index 9a6dd0c225..f2a4b767fd 100644 --- a/libs/image/tests/kis_meta_data_test.h +++ b/libs/image/tests/kis_meta_data_test.h @@ -1,53 +1,53 @@ /* * Copyright (c) 2008 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_META_DATA_TEST_H #define KIS_META_DATA_TEST_H #include namespace KisMetaData { class Value; } class KisMetaDataTest : public QObject { Q_OBJECT private Q_SLOTS: + void testSchemaBasic(); void testRationals(); void testValueCreation(); void testValueEquality(); void testValueCopy(); - void testSchemaBasic(); void testEntry(); void testStore(); void testFilters(); void testTypeInfo(); void testSchemaParse(); void testParser(); void testValidator(); private: KisMetaData::Value createRationalValue(); KisMetaData::Value createIntegerValue(int v = 42); KisMetaData::Value createStringValue(); KisMetaData::Value createListValue(); }; #endif diff --git a/libs/image/tests/kis_node_test.cpp b/libs/image/tests/kis_node_test.cpp index 2142e05b97..298abbabc6 100644 --- a/libs/image/tests/kis_node_test.cpp +++ b/libs/image/tests/kis_node_test.cpp @@ -1,457 +1,457 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_test.h" #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_node_graph_listener.h" #include #include "testutil.h" void KisNodeTest::testCreation() { TestUtil::TestGraphListener graphListener; KisNode * node = new TestNodeA(); QVERIFY(node->graphListener() == 0); node->setGraphListener(&graphListener); QVERIFY(node->graphListener() != 0); // Test contract for initial state QVERIFY(node->parent() == 0); QVERIFY(node->firstChild() == 0); QVERIFY(node->lastChild() == 0); QVERIFY(node->prevSibling() == 0); QVERIFY(node->nextSibling() == 0); QVERIFY(node->childCount() == 0); QVERIFY(node->at(0) == 0); QVERIFY(node->at(UINT_MAX) == 0); QVERIFY(node->index(0) == -1); delete node; } void KisNodeTest::testOrdering() { TestUtil::TestGraphListener graphListener; KisNodeSP root = new TestNodeA(); root->setGraphListener(&graphListener); KisNodeSP node1 = new TestNodeA(); KisNodeSP node2 = new TestNodeA(); KisNodeSP node3 = new TestNodeA(); KisNodeSP node4 = new TestNodeA(); /* +---------+ | node 4 | | node 2 | | node 3 | | node 1 | |root | +---------+ */ graphListener.resetBools(); QVERIFY(root->lastChild() == 0); root->add(node1, root->lastChild()); QVERIFY(graphListener.beforeInsertRow == true); QVERIFY(graphListener.afterInsertRow == true); QVERIFY(graphListener.beforeRemoveRow == false); QVERIFY(graphListener.afterRemoveRow == false); QVERIFY(root->firstChild() == node1); QVERIFY(root->lastChild() == node1); graphListener.resetBools(); QVERIFY(root->lastChild() == node1); root->add(node2, root->lastChild()); QVERIFY(graphListener.beforeInsertRow == true); QVERIFY(graphListener.afterInsertRow == true); QVERIFY(graphListener.beforeRemoveRow == false); QVERIFY(graphListener.afterRemoveRow == false); QVERIFY(root->firstChild() == node1); QVERIFY(root->lastChild() == node2); graphListener.resetBools(); QVERIFY(root->lastChild() == node2); root->add(node3, node1); QVERIFY(root->lastChild() == node2); QVERIFY(graphListener.beforeInsertRow == true); QVERIFY(graphListener.afterInsertRow == true); QVERIFY(graphListener.beforeRemoveRow == false); QVERIFY(graphListener.afterRemoveRow == false); QVERIFY(root->firstChild() == node1); QVERIFY(root->lastChild() == node2); graphListener.resetBools(); root->add(node4, root->lastChild()); QVERIFY(graphListener.beforeInsertRow == true); QVERIFY(graphListener.afterInsertRow == true); QVERIFY(graphListener.beforeRemoveRow == false); QVERIFY(graphListener.afterRemoveRow == false); QVERIFY(root->firstChild() == node1); QVERIFY(root->lastChild() == node4); graphListener.resetBools(); QVERIFY(root->childCount() == 4); QVERIFY(node1->parent() == root); QVERIFY(node2->parent() == root); QVERIFY(node3->parent() == root); QVERIFY(node4->parent() == root); QVERIFY(root->firstChild() == node1); QVERIFY(root->lastChild() == node4); QVERIFY(root->at(0) == node1); QVERIFY(root->at(1) == node3); QVERIFY(root->at(2) == node2); QVERIFY(root->at(3) == node4); QVERIFY(root->index(node1) == 0); QVERIFY(root->index(node3) == 1); QVERIFY(root->index(node2) == 2); QVERIFY(root->index(node4) == 3); QVERIFY(node4->prevSibling() == node2); QVERIFY(node3->prevSibling() == node1); QVERIFY(node2->prevSibling() == node3); QVERIFY(node1->prevSibling() == 0); QVERIFY(node4->nextSibling() == 0); QVERIFY(node3->nextSibling() == node2); QVERIFY(node2->nextSibling() == node4); QVERIFY(node1->nextSibling() == node3); /* +---------+ | node 3 | | node 4 | | node 2 | | node 1 | |root | +---------+ */ graphListener.resetBools(); QVERIFY(root->remove(root->at(3)) == true); QVERIFY(node4->parent() == 0); QVERIFY(graphListener.beforeInsertRow == false); QVERIFY(graphListener.afterInsertRow == false); QVERIFY(graphListener.beforeRemoveRow == true); QVERIFY(graphListener.afterRemoveRow == true); QVERIFY(root->childCount() == 3); QVERIFY(root->lastChild() == node2); QVERIFY(root->firstChild() == node1); QVERIFY(node4->prevSibling() == 0); QVERIFY(node4->nextSibling() == 0); graphListener.resetBools(); node3->add(node4, node3->lastChild()); QVERIFY(graphListener.beforeInsertRow == true); QVERIFY(graphListener.afterInsertRow == true); QVERIFY(graphListener.beforeRemoveRow == false); QVERIFY(graphListener.afterRemoveRow == false); QVERIFY(root->childCount() == 3); QVERIFY(root->lastChild() == node2); QVERIFY(root->firstChild() == node1); QVERIFY(node3->childCount() == 1); QVERIFY(node3->firstChild() == node4); QVERIFY(node3->lastChild() == node4); QVERIFY(node4->prevSibling() == 0); QVERIFY(node4->nextSibling() == 0); QVERIFY(root->remove(node4) == false); graphListener.resetBools(); node3->remove(node4); QVERIFY(graphListener.beforeInsertRow == false); QVERIFY(graphListener.afterInsertRow == false); QVERIFY(graphListener.beforeRemoveRow == true); QVERIFY(graphListener.afterRemoveRow == true); QVERIFY(node3->childCount() == 0); QVERIFY(node4->parent() == 0); QVERIFY(root->childCount() == 3); QVERIFY(root->lastChild() == node2); QVERIFY(root->firstChild() == node1); QVERIFY(node4->prevSibling() == 0); QVERIFY(node4->nextSibling() == 0); } void KisNodeTest::testSetDirty() { // Create a node graph with two branches /* node2 node4 node6 node5 node5 node3 node1 root */ KisNodeSP root = new TestNode(); root->setName("root"); KisNodeSP node1 = new TestNode(); node1->setName("node1"); QVERIFY(root->add(node1, 0)); KisNodeSP node2 = new TestNode(); node2->setName("node2"); QVERIFY(root->add(node2, node1)); KisNodeSP node3 = new TestNode(); node3->setName("node3"); QVERIFY(node1->add(node3, 0)); KisNodeSP node4 = new TestNode(); node4->setName("node4"); QVERIFY(node1->add(node4, node3)); KisNodeSP node5 = new TestNode(); node5->setName("node5"); QVERIFY(node3->add(node5, 0)); KisNodeSP node6 = new TestNode(); node6->setName("node6"); QVERIFY(node5->add(node6, 0)); KisNodeSP node7 = new TestNode(); node7->setName("node7"); QVERIFY(node6->add(node7, 0)); #if 0 // XXX: rewrite tests after redesign to update strategies node1->setDirty(); QVERIFY(node1->isDirty()); QVERIFY(!node2->isDirty()); QVERIFY(root->isDirty()); root->setClean(); QVERIFY(!root->isDirty()); node1->setClean(); QVERIFY(!node1->isDirty()); node7->setDirty(QRect(10, 10, 100, 100)); QVERIFY(node7->isDirty()); QVERIFY(node7->isDirty(QRect(5, 5, 15, 15))); QVERIFY(root->isDirty(QRect(5, 5, 15, 15))); QVERIFY(!root->isDirty(QRect(-10, -10, 20, 20))); QVERIFY(!node2->isDirty()); node7->setClean(QRect(10, 10, 10, 10)); QVERIFY(!node7->isDirty(QRect(10, 10, 10, 10))); QVERIFY(node7->isDirty()); QVERIFY(node7->isDirty(QRect(0, 0, 50, 50))); #endif } void KisNodeTest::testChildNodes() { KisNodeSP root = new TestNodeA(); KisNodeSP a = new TestNodeA(); root->add(a, 0); a->setVisible(true); a->setUserLocked(true); KisNodeSP b = new TestNodeB(); root->add(b, 0); b->setVisible(false); b->setUserLocked(true); KisNodeSP c = new TestNodeC(); root->add(c, 0); c->setVisible(false); c->setVisible(false); QList allNodes = root->childNodes(QStringList(), KoProperties()); QCOMPARE((int) allNodes.count(), 3); // a, b, c QStringList nodeTypes; nodeTypes << "TestNodeA" << "TestNodeB"; QList subSetOfNodeTypes = root->childNodes(nodeTypes, KoProperties()); - QVERIFY(subSetOfNodeTypes.count() == 2); // a, b + QCOMPARE(subSetOfNodeTypes.count(), 2); // a, b nodeTypes.clear(); nodeTypes << "TestNodeB" << "TestNodeC"; KoProperties props; props.setProperty("visible", false); props.setProperty("locked", true); QList subsetOfTypesAndProps = root->childNodes(nodeTypes, props); - QVERIFY(subsetOfTypesAndProps.count() == 1); // b + QCOMPARE(subsetOfTypesAndProps.count(), 1); // b KoProperties props2; - props.setProperty("visible", false); - QList subSetOfProps = root->childNodes(QStringList(), props); - QVERIFY(subSetOfProps.count() == 2); // b, c + props2.setProperty("visible", false); + QList subSetOfProps = root->childNodes(QStringList(), props2); + QCOMPARE(subSetOfProps.count(), 2); // b, c } void KisNodeTest::testDirtyRegion() { #if 0 // Rewrite KisNodeSP root = new TestNodeA(); root->setDirty(QRect(0, 0, 100, 100)); root->setDirty(QRect(50, 50, 100, 100)); QRegion dirtyRegion = root->dirtyRegion(QRect(0, 0, 200, 200)); QVector rects = dirtyRegion.rects(); QVERIFY(rects.count() == 3); QVERIFY(rects[0] == QRect(0, 0, 100, 50)); QVERIFY(rects[1] == QRect(0, 50, 150, 50)); QVERIFY(rects[2] == QRect(50, 100, 100, 50)); #endif } #define NUM_CYCLES 100000 #define NUM_THREADS 30 class KisNodeTest::VisibilityKiller : public QRunnable { public: VisibilityKiller(KisNodeSP victimNode, KisNodeSP nastyChild, bool /*isWriter*/) : m_victimNode(victimNode), m_nastyChild(nastyChild) {} void run() override { int visibility = 0; for(int i = 0; i < NUM_CYCLES; i++) { if(i % 3 == 0) { m_nastyChild->setVisible(visibility++ & 0x1); // dbgKrita << "visibility" << i << m_nastyChild->visible(); } else if (i%3 == 1){ KoProperties props; props.setProperty("visible", true); QList visibleNodes = m_victimNode->childNodes(QStringList("TestNodeB"), props); Q_FOREACH (KisNodeSP node, visibleNodes) { m_nastyChild->setVisible(visibility++ & 0x1); } // dbgKrita << visibleNodes; } else { Q_ASSERT(m_victimNode->firstChild()); Q_ASSERT(m_victimNode->lastChild()); m_victimNode->firstChild()->setVisible(visibility++ & 0x1); m_victimNode->lastChild()->setVisible(visibility++ & 0x1); } } } private: KisNodeSP m_victimNode; KisNodeSP m_nastyChild; }; template void KisNodeTest::propertiesStressTestImpl() { KisNodeSP root = new TestNodeA(); KisNodeSP a = new TestNodeA(); KisNodeSP b = new TestNodeB(); KisNodeSP c = new TestNodeC(); root->add(a, 0); root->add(b, 0); root->add(c, 0); a->setVisible(true); b->setVisible(true); c->setVisible(true); a->setUserLocked(true); b->setUserLocked(true); c->setUserLocked(true); QThreadPool threadPool; threadPool.setMaxThreadCount(NUM_THREADS); for(int i = 0; i< NUM_THREADS; i++) { KillerClass *killer = new KillerClass(root, b, i == 0); threadPool.start(killer); } threadPool.waitForDone(); } void KisNodeTest::propertiesStressTest() { propertiesStressTestImpl(); } class KisNodeTest::GraphKiller : public QRunnable { public: GraphKiller(KisNodeSP parentNode, KisNodeSP childNode, bool isWriter) : m_parentNode(parentNode), m_childNode(childNode), m_isWriter(isWriter) {} void run() override { int numCycles = qMax(10000, NUM_CYCLES / 100); for(int i = 0; i < numCycles; i++) { if (m_isWriter) { m_parentNode->remove(m_childNode); m_parentNode->add(m_childNode, 0); } else { KisNodeSP a = m_parentNode->firstChild(); KisNodeSP b = m_parentNode->lastChild(); if (a) { a->parent(); a->nextSibling(); a->prevSibling(); } if (b) { b->parent(); b->nextSibling(); b->prevSibling(); } m_parentNode->at(0); m_parentNode->index(m_childNode); } if (i % 1000 == 0) { //dbgKrita << "Alive"; } } } private: KisNodeSP m_parentNode; KisNodeSP m_childNode; bool m_isWriter; }; void KisNodeTest::graphStressTest() { propertiesStressTestImpl(); } QTEST_MAIN(KisNodeTest) diff --git a/libs/image/tests/kis_painter_test.cpp b/libs/image/tests/kis_painter_test.cpp index 73657c897f..2050370899 100644 --- a/libs/image/tests/kis_painter_test.cpp +++ b/libs/image/tests/kis_painter_test.cpp @@ -1,867 +1,688 @@ /* * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_painter_test.h" #include #include #include #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_types.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include #include "testutil.h" #include void KisPainterTest::allCsApplicator(void (KisPainterTest::* funcPtr)(const KoColorSpace*cs)) { - QList colorsapces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); + qDebug() << qAppName(); - Q_FOREACH (const KoColorSpace* cs, colorsapces) { + QList colorspaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); + + Q_FOREACH (const KoColorSpace* cs, colorspaces) { QString csId = cs->id(); // ALL THESE COLORSPACES ARE BROKEN: WE NEED UNITTESTS FOR COLORSPACES! if (csId.startsWith("KS")) continue; if (csId.startsWith("Xyz")) continue; if (csId.startsWith('Y')) continue; if (csId.contains("AF")) continue; if (csId == "GRAYU16") continue; // No point in testing bounds with a cs without alpha if (csId == "GRAYU8") continue; // No point in testing bounds with a cs without alpha dbgKrita << "Testing with cs" << csId; if (cs && cs->compositeOp(COMPOSITE_OVER) != 0) { (this->*funcPtr)(cs); } else { dbgKrita << "Cannot bitBlt for cs" << csId; } } } void KisPainterTest::testSimpleBlt(const KoColorSpace * cs) { KisPaintDeviceSP dst = new KisPaintDevice(cs); KisPaintDeviceSP src = new KisPaintDevice(cs); KoColor c(Qt::red, cs); c.setOpacity(quint8(128)); src->fill(20, 20, 20, 20, c.data()); QCOMPARE(src->exactBounds(), QRect(20, 20, 20, 20)); const KoCompositeOp* op; { op = cs->compositeOp(COMPOSITE_OVER); KisPainter painter(dst); painter.setCompositeOp(op); painter.bitBlt(50, 50, src, 20, 20, 20, 20); painter.end(); QCOMPARE(dst->exactBounds(), QRect(50,50,20,20)); } dst->clear(); { op = cs->compositeOp(COMPOSITE_COPY); KisPainter painter(dst); painter.setCompositeOp(op); painter.bitBlt(50, 50, src, 20, 20, 20, 20); painter.end(); QCOMPARE(dst->exactBounds(), QRect(50,50,20,20)); } } void KisPainterTest::testSimpleBlt() { allCsApplicator(&KisPainterTest::testSimpleBlt); } /* Note: the bltSelection tests assume the following geometry: 0,0 0,30 +---------+------+ | 10,10 | | | +----+ | | |####| | | |####| | +----+----+ | | 20,20 | | | | | +----------------+ 30,30 */ void KisPainterTest::testPaintDeviceBltSelection(const KoColorSpace * cs) { KisPaintDeviceSP dst = new KisPaintDevice(cs); KisPaintDeviceSP src = new KisPaintDevice(cs); KoColor c(Qt::red, cs); c.setOpacity(quint8(128)); src->fill(0, 0, 20, 20, c.data()); QCOMPARE(src->exactBounds(), QRect(0, 0, 20, 20)); KisSelectionSP selection = new KisSelection(); selection->pixelSelection()->select(QRect(10, 10, 20, 20)); selection->updateProjection(); QCOMPARE(selection->selectedExactRect(), QRect(10, 10, 20, 20)); KisPainter painter(dst); painter.setSelection(selection); painter.bitBlt(0, 0, src, 0, 0, 30, 30); painter.end(); QImage image = dst->convertToQImage(0); image.save("blt_Selection_" + cs->name() + ".png"); QCOMPARE(dst->exactBounds(), QRect(10, 10, 10, 10)); const KoCompositeOp* op = cs->compositeOp(COMPOSITE_SUBTRACT); if (op->id() == COMPOSITE_SUBTRACT) { KisPaintDeviceSP dst2 = new KisPaintDevice(cs); KisPainter painter2(dst2); painter2.setSelection(selection); painter2.setCompositeOp(op); painter2.bitBlt(0, 0, src, 0, 0, 30, 30); painter2.end(); QCOMPARE(dst2->exactBounds(), QRect(10, 10, 10, 10)); } } void KisPainterTest::testPaintDeviceBltSelection() { allCsApplicator(&KisPainterTest::testPaintDeviceBltSelection); } void KisPainterTest::testPaintDeviceBltSelectionIrregular(const KoColorSpace * cs) { KisPaintDeviceSP dst = new KisPaintDevice(cs); KisPaintDeviceSP src = new KisPaintDevice(cs); KisFillPainter gc(src); gc.fillRect(0, 0, 20, 20, KoColor(Qt::red, cs)); gc.end(); QCOMPARE(src->exactBounds(), QRect(0, 0, 20, 20)); KisSelectionSP sel = new KisSelection(); KisPixelSelectionSP psel = sel->pixelSelection(); psel->select(QRect(10, 15, 20, 15)); psel->select(QRect(15, 10, 15, 5)); QCOMPARE(psel->selectedExactRect(), QRect(10, 10, 20, 20)); QCOMPARE(TestUtil::alphaDevicePixel(psel, 13, 13), MIN_SELECTED); KisPainter painter(dst); painter.setSelection(sel); painter.bitBlt(0, 0, src, 0, 0, 30, 30); painter.end(); QImage image = dst->convertToQImage(0); image.save("blt_Selection_irregular" + cs->name() + ".png"); QCOMPARE(dst->exactBounds(), QRect(10, 10, 10, 10)); Q_FOREACH (KoChannelInfo * channel, cs->channels()) { // Only compare alpha if there actually is an alpha channel in // this colorspace if (channel->channelType() == KoChannelInfo::ALPHA) { QColor c; dst->pixel(13, 13, &c); QCOMPARE((int) c.alpha(), (int) OPACITY_TRANSPARENT_U8); } } } void KisPainterTest::testPaintDeviceBltSelectionIrregular() { allCsApplicator(&KisPainterTest::testPaintDeviceBltSelectionIrregular); } void KisPainterTest::testPaintDeviceBltSelectionInverted(const KoColorSpace * cs) { KisPaintDeviceSP dst = new KisPaintDevice(cs); KisPaintDeviceSP src = new KisPaintDevice(cs); KisFillPainter gc(src); gc.fillRect(0, 0, 30, 30, KoColor(Qt::red, cs)); gc.end(); QCOMPARE(src->exactBounds(), QRect(0, 0, 30, 30)); KisSelectionSP sel = new KisSelection(); KisPixelSelectionSP psel = sel->pixelSelection(); psel->select(QRect(10, 10, 20, 20)); psel->invert(); sel->updateProjection(); KisPainter painter(dst); painter.setSelection(sel); painter.bitBlt(0, 0, src, 0, 0, 30, 30); painter.end(); QCOMPARE(dst->exactBounds(), QRect(0, 0, 30, 30)); } void KisPainterTest::testPaintDeviceBltSelectionInverted() { allCsApplicator(&KisPainterTest::testPaintDeviceBltSelectionInverted); } void KisPainterTest::testSelectionBltSelection() { KisPixelSelectionSP src = new KisPixelSelection(); src->select(QRect(0, 0, 20, 20)); QCOMPARE(src->selectedExactRect(), QRect(0, 0, 20, 20)); KisSelectionSP sel = new KisSelection(); KisPixelSelectionSP Selection = sel->pixelSelection(); Selection->select(QRect(10, 10, 20, 20)); QCOMPARE(Selection->selectedExactRect(), QRect(10, 10, 20, 20)); sel->updateProjection(); KisPixelSelectionSP dst = new KisPixelSelection(); KisPainter painter(dst); painter.setSelection(sel); painter.bitBlt(0, 0, src, 0, 0, 30, 30); painter.end(); QCOMPARE(dst->selectedExactRect(), QRect(10, 10, 10, 10)); KisSequentialConstIterator it(dst, QRect(10, 10, 10, 10)); while (it.nextPixel()) { // These are selections, so only one channel and it should // be totally selected QCOMPARE(it.oldRawData()[0], MAX_SELECTED); } } /* Test with non-square selection 0,0 0,30 +-----------+------+ | 13,13 | | | x +--+ | | +--+##| | | |#####| | +-----+-----+ | | 20,20 | | | | | +------------------+ 30,30 */ void KisPainterTest::testSelectionBltSelectionIrregular() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); KisPixelSelectionSP src = new KisPixelSelection(); src->select(QRect(0, 0, 20, 20)); QCOMPARE(src->selectedExactRect(), QRect(0, 0, 20, 20)); KisSelectionSP sel = new KisSelection(); KisPixelSelectionSP Selection = sel->pixelSelection(); Selection->select(QRect(10, 15, 20, 15)); Selection->select(QRect(15, 10, 15, 5)); QCOMPARE(Selection->selectedExactRect(), QRect(10, 10, 20, 20)); QCOMPARE(TestUtil::alphaDevicePixel(Selection, 13, 13), MIN_SELECTED); sel->updateProjection(); KisPixelSelectionSP dst = new KisPixelSelection(); KisPainter painter(dst); painter.setSelection(sel); painter.bitBlt(0, 0, src, 0, 0, 30, 30); painter.end(); QCOMPARE(dst->selectedExactRect(), QRect(10, 10, 10, 10)); QCOMPARE(TestUtil::alphaDevicePixel(dst, 13, 13), MIN_SELECTED); } void KisPainterTest::testSelectionBitBltFixedSelection() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dst = new KisPaintDevice(cs); KisPaintDeviceSP src = new KisPaintDevice(cs); KoColor c(Qt::red, cs); c.setOpacity(quint8(128)); src->fill(0, 0, 20, 20, c.data()); QCOMPARE(src->exactBounds(), QRect(0, 0, 20, 20)); KisFixedPaintDeviceSP fixedSelection = new KisFixedPaintDevice(cs); fixedSelection->setRect(QRect(0, 0, 20, 20)); fixedSelection->initialize(); KoColor fill(Qt::white, cs); fixedSelection->fill(5, 5, 10, 10, fill.data()); fixedSelection->convertTo(KoColorSpaceRegistry::instance()->alpha8()); KisPainter painter(dst); painter.bitBltWithFixedSelection(0, 0, src, fixedSelection, 20, 20); painter.end(); QCOMPARE(dst->exactBounds(), QRect(5, 5, 10, 10)); /* dbgKrita << "canary1.5"; dst->clear(); painter.begin(dst); painter.bitBltWithFixedSelection(0, 0, src, fixedSelection, 10, 20); painter.end(); dbgKrita << "canary2"; QCOMPARE(dst->exactBounds(), QRect(5, 5, 5, 10)); dst->clear(); painter.begin(dst); painter.bitBltWithFixedSelection(0, 0, src, fixedSelection, 5, 5, 5, 5, 10, 20); painter.end(); dbgKrita << "canary3"; QCOMPARE(dst->exactBounds(), QRect(5, 5, 5, 10)); dst->clear(); painter.begin(dst); painter.bitBltWithFixedSelection(5, 5, src, fixedSelection, 10, 20); painter.end(); dbgKrita << "canary4"; QCOMPARE(dst->exactBounds(), QRect(10, 10, 5, 10)); */ } void KisPainterTest::testSelectionBitBltEraseCompositeOp() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dst = new KisPaintDevice(cs); KoColor c(Qt::red, cs); dst->fill(0, 0, 150, 150, c.data()); KisPaintDeviceSP src = new KisPaintDevice(cs); KoColor c2(Qt::black, cs); src->fill(50, 50, 50, 50, c2.data()); KisSelectionSP sel = new KisSelection(); KisPixelSelectionSP selection = sel->pixelSelection(); selection->select(QRect(25, 25, 100, 100)); sel->updateProjection(); const KoCompositeOp* op = cs->compositeOp(COMPOSITE_ERASE); KisPainter painter(dst); painter.setSelection(sel); painter.setCompositeOp(op); painter.bitBlt(0, 0, src, 0, 0, 150, 150); painter.end(); //dst->convertToQImage(0).save("result.png"); QRect erasedRect(50, 50, 50, 50); KisSequentialConstIterator it(dst, QRect(0, 0, 150, 150)); while (it.nextPixel()) { if(!erasedRect.contains(it.x(), it.y())) { QVERIFY(memcmp(it.oldRawData(), c.data(), cs->pixelSize()) == 0); } } } void KisPainterTest::testSimpleAlphaCopy() { KisPaintDeviceSP src = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); KisPaintDeviceSP dst = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); quint8 p = 128; src->fill(0, 0, 100, 100, &p); QVERIFY(src->exactBounds() == QRect(0, 0, 100, 100)); KisPainter gc(dst); gc.setCompositeOp(KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(QPoint(0, 0), src, src->exactBounds()); gc.end(); QCOMPARE(dst->exactBounds(), QRect(0, 0, 100, 100)); } void KisPainterTest::checkPerformance() { KisPaintDeviceSP src = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); KisPaintDeviceSP dst = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); quint8 p = 128; src->fill(0, 0, 10000, 5000, &p); KisSelectionSP sel = new KisSelection(); sel->pixelSelection()->select(QRect(0, 0, 10000, 5000), 128); sel->updateProjection(); QTime t; t.start(); for (int i = 0; i < 10; ++i) { KisPainter gc(dst); gc.bitBlt(0, 0, src, 0, 0, 10000, 5000); } t.restart(); for (int i = 0; i < 10; ++i) { KisPainter gc(dst, sel); gc.bitBlt(0, 0, src, 0, 0, 10000, 5000); } } void KisPainterTest::testBitBltOldData() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); KisPaintDeviceSP src = new KisPaintDevice(cs); KisPaintDeviceSP dst = new KisPaintDevice(cs); quint8 defaultPixel = 0; quint8 p1 = 128; quint8 p2 = 129; quint8 p3 = 130; KoColor defaultColor(&defaultPixel, cs); KoColor color1(&p1, cs); KoColor color2(&p2, cs); KoColor color3(&p3, cs); QRect fillRect(0,0,5000,5000); src->fill(fillRect, color1); KisPainter srcGc(src); srcGc.beginTransaction(); src->fill(fillRect, color2); KisPainter dstGc(dst); dstGc.bitBltOldData(QPoint(), src, fillRect); QVERIFY(TestUtil::checkAlphaDeviceFilledWithPixel(dst, fillRect, p1)); dstGc.end(); srcGc.deleteTransaction(); } -void KisPainterTest::benchmarkBitBlt() -{ - quint8 p = 128; - const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); - - KisPaintDeviceSP src = new KisPaintDevice(cs); - KisPaintDeviceSP dst = new KisPaintDevice(cs); - - KoColor color(&p, cs); - QRect fillRect(0,0,5000,5000); - - src->fill(fillRect, color); - - QBENCHMARK { - KisPainter gc(dst); - gc.bitBlt(QPoint(), src, fillRect); - } -} - -void KisPainterTest::benchmarkBitBltOldData() -{ - quint8 p = 128; - const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); - - KisPaintDeviceSP src = new KisPaintDevice(cs); - KisPaintDeviceSP dst = new KisPaintDevice(cs); - - KoColor color(&p, cs); - QRect fillRect(0,0,5000,5000); - - src->fill(fillRect, color); - - QBENCHMARK { - KisPainter gc(dst); - gc.bitBltOldData(QPoint(), src, fillRect); - } -} #include "kis_paint_device_debug_utils.h" #include "KisRenderedDab.h" void testMassiveBltFixedImpl(int numRects, bool varyOpacity = false, bool useSelection = false) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dst = new KisPaintDevice(cs); QList colors; colors << Qt::red; colors << Qt::green; colors << Qt::blue; QRect devicesRect; QList devices; for (int i = 0; i < numRects; i++) { const QRect rc(10 + i * 10, 10 + i * 10, 30, 30); KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs); dev->setRect(rc); dev->initialize(); dev->fill(rc, KoColor(colors[i % 3], cs)); dev->fill(kisGrowRect(rc, -5), KoColor(Qt::white, cs)); KisRenderedDab dab; dab.device = dev; dab.offset = dev->bounds().topLeft(); dab.opacity = varyOpacity ? qreal(1 + i) / numRects : 1.0; dab.flow = 1.0; devices << dab; devicesRect |= rc; } KisSelectionSP selection; if (useSelection) { selection = new KisSelection(); selection->pixelSelection()->select(kisGrowRect(devicesRect, -7)); } const QString opacityPostfix = varyOpacity ? "_varyop" : ""; const QString selectionPostfix = useSelection ? "_sel" : ""; const QRect fullRect = kisGrowRect(devicesRect, 10); { KisPainter painter(dst); painter.setSelection(selection); painter.bltFixed(fullRect, devices); painter.end(); QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect), "kispainter_test", "massive_bitblt", QString("full_update_%1%2%3") .arg(numRects) .arg(opacityPostfix) .arg(selectionPostfix), 1, 1)); } dst->clear(); { KisPainter painter(dst); painter.setSelection(selection); for (int i = fullRect.x(); i <= fullRect.center().x(); i += 10) { const QRect rc(i, fullRect.y(), 10, fullRect.height()); painter.bltFixed(rc, devices); } painter.end(); QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect), "kispainter_test", "massive_bitblt", QString("partial_update_%1%2%3") .arg(numRects) .arg(opacityPostfix) .arg(selectionPostfix))); } } void KisPainterTest::testMassiveBltFixedSingleTile() { testMassiveBltFixedImpl(3); } void KisPainterTest::testMassiveBltFixedMultiTile() { testMassiveBltFixedImpl(6); } void KisPainterTest::testMassiveBltFixedMultiTileWithOpacity() { testMassiveBltFixedImpl(6, true); } void KisPainterTest::testMassiveBltFixedMultiTileWithSelection() { testMassiveBltFixedImpl(6, false, true); } void KisPainterTest::testMassiveBltFixedCornerCases() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dst = new KisPaintDevice(cs); QList devices; QVERIFY(dst->extent().isEmpty()); { // empty devices, shouldn't crash KisPainter painter(dst); painter.bltFixed(QRect(60,60,20,20), devices); painter.end(); } QVERIFY(dst->extent().isEmpty()); const QRect rc(10,10,20,20); KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs); dev->setRect(rc); dev->initialize(); dev->fill(rc, KoColor(Qt::white, cs)); devices.append(KisRenderedDab(dev)); { // rect outside the devices bounds, shouldn't crash KisPainter painter(dst); painter.bltFixed(QRect(60,60,20,20), devices); painter.end(); } QVERIFY(dst->extent().isEmpty()); } -#include "kis_paintop_utils.h" -#include "kis_algebra_2d.h" - -void benchmarkMassiveBltFixedImpl(int numDabs, int size, qreal spacing, int idealNumPatches, Qt::Orientations direction) -{ - const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); - KisPaintDeviceSP dst = new KisPaintDevice(cs); - - QList colors; - colors << QColor(255, 0, 0, 200); - colors << QColor(0, 255, 0, 200); - colors << QColor(0, 0, 255, 200); - - QRect devicesRect; - QList devices; - - const int step = spacing * size; - - for (int i = 0; i < numDabs; i++) { - const QRect rc = - direction == Qt::Horizontal ? QRect(10 + i * step, 0, size, size) : - direction == Qt::Vertical ? QRect(0, 10 + i * step, size, size) : - QRect(10 + i * step, 10 + i * step, size, size); - - KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs); - dev->setRect(rc); - dev->initialize(); - dev->fill(rc, KoColor(colors[i % 3], cs)); - dev->fill(kisGrowRect(rc, -5), KoColor(Qt::white, cs)); - - KisRenderedDab dab; - dab.device = dev; - dab.offset = dev->bounds().topLeft(); - dab.opacity = 1.0; - dab.flow = 1.0; - - devices << dab; - devicesRect |= rc; - } - - const QRect fullRect = kisGrowRect(devicesRect, 10); - - { - KisPainter painter(dst); - painter.bltFixed(fullRect, devices); - painter.end(); - //QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect), - // "kispainter_test", - // "massive_bitblt_benchmark", - // "initial")); - dst->clear(); - } - - - QVector dabRects; - Q_FOREACH (const KisRenderedDab &dab, devices) { - dabRects.append(dab.realBounds()); - } - - QElapsedTimer t; - - qint64 massiveTime = 0; - int massiveTries = 0; - int numRects = 0; - int avgPatchSize = 0; - - for (int i = 0; i < 50 || massiveTime > 5000000; i++) { - QVector rects = KisPaintOpUtils::splitDabsIntoRects(dabRects, idealNumPatches, size, spacing); - numRects = rects.size(); - - // HACK: please calculate real *average*! - avgPatchSize = KisAlgebra2D::maxDimension(rects.first()); - - t.start(); - - KisPainter painter(dst); - Q_FOREACH (const QRect &rc, rects) { - painter.bltFixed(rc, devices); - } - painter.end(); - - massiveTime += t.nsecsElapsed() / 1000; - massiveTries++; - dst->clear(); - } - - qint64 linearTime = 0; - int linearTries = 0; - - for (int i = 0; i < 50 || linearTime > 5000000; i++) { - t.start(); - - KisPainter painter(dst); - Q_FOREACH (const KisRenderedDab &dab, devices) { - painter.setOpacity(255 * dab.opacity); - painter.setFlow(255 * dab.flow); - painter.bltFixed(dab.offset, dab.device, dab.device->bounds()); - } - painter.end(); - - linearTime += t.nsecsElapsed() / 1000; - linearTries++; - dst->clear(); - } - - const qreal avgMassive = qreal(massiveTime) / massiveTries; - const qreal avgLinear = qreal(linearTime) / linearTries; - - const QString directionMark = - direction == Qt::Horizontal ? "H" : - direction == Qt::Vertical ? "V" : "D"; - - qDebug() - << "D:" << size - << "S:" << spacing - << "N:" << numDabs - << "P (px):" << avgPatchSize - << "R:" << numRects - << "Dir:" << directionMark - << "\t" - << qPrintable(QString("Massive (usec): %1").arg(QString::number(avgMassive, 'f', 2), 8)) - << "\t" - << qPrintable(QString("Linear (usec): %1").arg(QString::number(avgLinear, 'f', 2), 8)) - << (avgMassive < avgLinear ? "*" : " ") - << qPrintable(QString("%1") - .arg(QString::number((avgMassive - avgLinear) / avgLinear * 100.0, 'f', 2), 8)) - << qRound(size + size * spacing * (numDabs - 1)); -} - - -void KisPainterTest::benchmarkMassiveBltFixed() -{ - const qreal sp = 0.14; - const int idealThreadCount = 8; - - for (int d = 50; d < 301; d += 50) { - for (int n = 1; n < 150; n = qCeil(n * 1.5)) { - benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Horizontal); - benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Vertical); - benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Vertical | Qt::Horizontal); - } - } -} - #include "kis_lod_transform.h" inline QRect extentifyRect(const QRect &rc) { return KisLodTransform::alignedRect(rc, 6); } void testOptimizedCopyingImpl(const QRect &srcRect, const QRect &dstRect, const QRect &srcCopyRect, const QPoint &dstPt, const QRect &expectedDstBounds) { const QRect expectedDstExtent = extentifyRect(expectedDstBounds); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP src = new KisPaintDevice(cs); KisPaintDeviceSP dst = new KisPaintDevice(cs); const KoColor color1(Qt::red, cs); const KoColor color2(Qt::blue, cs); src->fill(srcRect, color1); dst->fill(dstRect, color2); KisPainter::copyAreaOptimized(dstPt, src, dst, srcCopyRect); //KIS_DUMP_DEVICE_2(dst, QRect(0,0,5000,5000), "dst", "dd"); QCOMPARE(dst->exactBounds(), expectedDstBounds); QCOMPARE(dst->extent(), expectedDstExtent); } void KisPainterTest::testOptimizedCopying() { const QRect srcRect(1000, 1000, 1000, 1000); const QRect srcCopyRect(0, 0, 5000, 5000); testOptimizedCopyingImpl(srcRect, QRect(6000, 500, 1000,1000), srcCopyRect, srcCopyRect.topLeft(), QRect(1000, 500, 6000, 1500)); testOptimizedCopyingImpl(srcRect, QRect(4500, 1500, 1000, 1000), srcCopyRect, srcCopyRect.topLeft(), QRect(1000, 1000, 4500, 1500)); testOptimizedCopyingImpl(srcRect, QRect(2500, 2500, 1000, 1000), srcCopyRect, srcCopyRect.topLeft(), srcRect); testOptimizedCopyingImpl(srcRect, QRect(1200, 1200, 600, 1600), srcCopyRect, srcCopyRect.topLeft(), srcRect); testOptimizedCopyingImpl(srcRect, QRect(1200, 1200, 600, 600), srcCopyRect, srcCopyRect.topLeft(), srcRect); } -QTEST_MAIN(KisPainterTest) +KISTEST_MAIN(KisPainterTest) diff --git a/libs/image/tests/kis_painter_test.h b/libs/image/tests/kis_painter_test.h index cb908a37d0..16febddc16 100644 --- a/libs/image/tests/kis_painter_test.h +++ b/libs/image/tests/kis_painter_test.h @@ -1,72 +1,69 @@ /* * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTER_TEST_H #define KIS_PAINTER_TEST_H #include class KoColorSpace; class KisPainterTest : public QObject { Q_OBJECT private: void allCsApplicator(void (KisPainterTest::* funcPtr)(const KoColorSpace*cs)); void testSimpleBlt(const KoColorSpace * cs); void testPaintDeviceBltSelection(const KoColorSpace * cs); void testPaintDeviceBltSelectionIrregular(const KoColorSpace * cs); void testPaintDeviceBltSelectionInverted(const KoColorSpace * cs); void checkPerformance(); private Q_SLOTS: void testSimpleBlt(); void testSelectionBltSelectionIrregular(); // Irregular selection void testPaintDeviceBltSelectionInverted(); // Inverted selection void testPaintDeviceBltSelectionIrregular(); // Irregular selection void testPaintDeviceBltSelection(); // Square selection void testSelectionBltSelection(); // Square selection void testSimpleAlphaCopy(); void testSelectionBitBltFixedSelection(); void testSelectionBitBltEraseCompositeOp(); void testBitBltOldData(); - void benchmarkBitBlt(); - void benchmarkBitBltOldData(); void testMassiveBltFixedSingleTile(); void testMassiveBltFixedMultiTile(); void testMassiveBltFixedMultiTileWithOpacity(); void testMassiveBltFixedMultiTileWithSelection(); void testMassiveBltFixedCornerCases(); - void benchmarkMassiveBltFixed(); void testOptimizedCopying(); }; #endif diff --git a/libs/image/tests/kis_projection_leaf_test.cpp b/libs/image/tests/kis_projection_leaf_test.cpp index e8a94302c1..37835b458d 100644 --- a/libs/image/tests/kis_projection_leaf_test.cpp +++ b/libs/image/tests/kis_projection_leaf_test.cpp @@ -1,290 +1,468 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_projection_leaf_test.h" #include + +#define USE_DOCUMENT 0 #include "qimage_based_test.h" + #include "kis_projection_leaf.h" #include "kis_group_layer.h" struct TestImage : TestUtil::QImageBasedTest { TestImage() : TestUtil::QImageBasedTest("") { undoStore = new KisSurrogateUndoStore(); image = createImage(undoStore); addGlobalSelection(image); } KisSurrogateUndoStore *undoStore; KisImageSP image; KisNodeSP findBlur1() { return findNode(image->root(), "blur1"); } KisNodeSP findClone1() { return findNode(image->root(), "clone1"); } KisNodeSP findPaint1() { return findNode(image->root(), "paint1"); } }; bool safeCompare(KisProjectionLeafSP leaf, KisNodeSP node) { + if (node && node->inherits("KisSelectionMask")) { + return !leaf; + } + return (!leaf && !node) || (leaf->node() == node); } void checkNode(KisNodeSP node, const QString &prefix) { - dbgKrita << prefix << node->name(); + qDebug() << prefix << node->name(); + + if (!node->inherits("KisSelectionMask")) { + safeCompare(node->projectionLeaf()->parent(), node->parent()); + safeCompare(node->projectionLeaf()->prevSibling(), node->prevSibling()); + safeCompare(node->projectionLeaf()->nextSibling(), node->nextSibling()); + } - safeCompare(node->projectionLeaf()->parent(), node->parent()); safeCompare(node->projectionLeaf()->firstChild(), node->firstChild()); safeCompare(node->projectionLeaf()->lastChild(), node->lastChild()); - safeCompare(node->projectionLeaf()->prevSibling(), node->prevSibling()); - safeCompare(node->projectionLeaf()->nextSibling(), node->nextSibling()); QCOMPARE(node->projectionLeaf()->node(), node); KisNodeSP prevNode = node->lastChild(); while(prevNode) { checkNode(prevNode, QString("\"\"%1").arg(prefix)); prevNode = prevNode->prevSibling(); } } void printNodes(KisNodeSP node, const QString &prefix = "") { - dbgKrita << prefix << node->name(); + qDebug() << prefix << node->name(); KisNodeSP prevNode = node->lastChild(); while(prevNode) { printNodes(prevNode, QString("\"\"%1").arg(prefix)); prevNode = prevNode->prevSibling(); } } void printLeafsBackward(KisProjectionLeafSP leaf, QList &refNodes, const QString &prefix = "") { - dbgKrita << prefix << leaf->node()->name(); + qDebug() << prefix << leaf->node()->name(); QCOMPARE(leaf->node()->name(), refNodes.takeFirst()); KisProjectionLeafSP prevLeaf = leaf->lastChild(); while(prevLeaf) { printLeafsBackward(prevLeaf, refNodes, QString("\"\"%1").arg(prefix)); prevLeaf = prevLeaf->prevSibling(); } if (prefix == "") { QVERIFY(refNodes.isEmpty()); } } void printLeafsForward(KisProjectionLeafSP leaf, QList &refNodes, const QString &prefix = "") { - dbgKrita << prefix << leaf->node()->name(); + qDebug() << prefix << leaf->node()->name(); QCOMPARE(leaf->node()->name(), refNodes.takeFirst()); KisProjectionLeafSP prevLeaf = leaf->firstChild(); while(prevLeaf) { printLeafsForward(prevLeaf, refNodes, QString("\"\"%1").arg(prefix)); prevLeaf = prevLeaf->nextSibling(); } } void printParents(KisProjectionLeafSP leaf, QList &refNodes, const QString &prefix = "") { - dbgKrita << prefix << leaf->node()->name(); + qDebug() << prefix << leaf->node()->name(); QCOMPARE(leaf->node()->name(), refNodes.takeFirst()); leaf = leaf->parent(); if (leaf) { printParents(leaf, refNodes, QString("\"\"%1").arg(prefix)); } + + QVERIFY(refNodes.isEmpty()); } void KisProjectionLeafTest::test() { TestImage t; checkNode(t.image->root(), ""); } void KisProjectionLeafTest::testPassThrough() { TestImage t; KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); KisPaintLayerSP paint2 = new KisPaintLayer(t.image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP paint3 = new KisPaintLayer(t.image, "paint3", OPACITY_OPAQUE_U8); KisPaintLayerSP paint4 = new KisPaintLayer(t.image, "paint4", OPACITY_OPAQUE_U8); group1->setPassThroughMode(true); t.image->addNode(group1, t.image->root(), t.findBlur1()); t.image->addNode(paint2, group1); t.image->addNode(paint3, group1); t.image->addNode(paint4, group1); //checkNode(t.image->root(), ""); - dbgKrita << "== Nodes"; + qDebug() << "== Nodes"; printNodes(t.image->root()); { - dbgKrita << "== Leafs backward"; + qDebug() << "== Leafs backward"; QList refNodes; refNodes << "root" - << "selection" << "paint1" << "tmask1" << "group1" << "paint4" << "paint3" << "paint2" << "blur1" << "clone1"; printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); } { - dbgKrita << "== Leafs forward"; + qDebug() << "== Leafs forward"; QList refNodes; refNodes << "root" << "clone1" << "blur1" << "paint2" << "paint3" << "paint4" << "group1" << "paint1" - << "tmask1" - << "selection"; + << "tmask1"; printLeafsForward(t.image->root()->projectionLeaf(), refNodes); } { - dbgKrita << "== Parents for paint4"; + qDebug() << "== Parents for paint4"; QList refNodes; refNodes << "paint4" << "root"; printParents(paint4->projectionLeaf(), refNodes); } { - dbgKrita << "== Parents for paint3"; + qDebug() << "== Parents for paint3"; QList refNodes; refNodes << "paint3" << "root"; printParents(paint3->projectionLeaf(), refNodes); } { - dbgKrita << "== Parents for group1"; + qDebug() << "== Parents for group1"; QList refNodes; refNodes << "group1" << "root"; printParents(group1->projectionLeaf(), refNodes); } } void KisProjectionLeafTest::testNestedPassThrough() { TestImage t; KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); KisGroupLayerSP group2 = new KisGroupLayer(t.image, "group2", OPACITY_OPAQUE_U8); KisGroupLayerSP group3 = new KisGroupLayer(t.image, "group3", OPACITY_OPAQUE_U8); KisPaintLayerSP paint4 = new KisPaintLayer(t.image, "paint4", OPACITY_OPAQUE_U8); KisPaintLayerSP paint5 = new KisPaintLayer(t.image, "paint5", OPACITY_OPAQUE_U8); group1->setPassThroughMode(true); group2->setPassThroughMode(true); group3->setPassThroughMode(true); t.image->addNode(group1, t.image->root(), t.findBlur1()); t.image->addNode(group2, group1); t.image->addNode(paint4, group2); t.image->addNode(group3, t.image->root(), t.findBlur1()); t.image->addNode(paint5, group3); //checkNode(t.image->root(), ""); - dbgKrita << "== Nodes"; + qDebug() << "== Nodes"; printNodes(t.image->root()); { - dbgKrita << "== Leafs backward"; + qDebug() << "== Leafs backward"; QList refNodes; refNodes << "root" - << "selection" << "paint1" << "tmask1" << "group1" << "group2" <<"paint4" << "group3" << "paint5" << "blur1" << "clone1"; printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); } { - dbgKrita << "== Leafs forward"; + qDebug() << "== Leafs forward"; QList refNodes; refNodes << "root" << "clone1" << "blur1" << "paint5" << "group3" << "paint4" << "group2" << "group1" << "paint1" - << "tmask1" - << "selection"; + << "tmask1"; printLeafsForward(t.image->root()->projectionLeaf(), refNodes); } { - dbgKrita << "== Parents for paint4"; + qDebug() << "== Parents for paint4"; QList refNodes; refNodes << "paint4" << "root"; printParents(paint4->projectionLeaf(), refNodes); } { - dbgKrita << "== Parents for paint5"; + qDebug() << "== Parents for paint5"; QList refNodes; refNodes << "paint5" << "root"; printParents(paint5->projectionLeaf(), refNodes); } { - dbgKrita << "== Parents for group1"; + qDebug() << "== Parents for group1"; QList refNodes; refNodes << "group1" << "root"; printParents(group1->projectionLeaf(), refNodes); } } +#include "kis_selection_mask.h" +#include "kis_transparency_mask.h" + +void KisProjectionLeafTest::testSkippedSelectionMasks() +{ + TestImage t; + + KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); + KisPaintLayerSP paint2 = new KisPaintLayer(t.image, "paint2", OPACITY_OPAQUE_U8); + KisSelectionMaskSP selection3 = new KisSelectionMask(t.image); + selection3->setName("selection3"); + + KisPaintLayerSP paint4 = new KisPaintLayer(t.image, "paint4", OPACITY_OPAQUE_U8); + + KisTransparencyMaskSP tmask5 = new KisTransparencyMask(); + tmask5->setName("tmask5"); + + KisSelectionMaskSP selection6 = new KisSelectionMask(t.image); + selection6->setName("selection6"); + + KisTransparencyMaskSP tmask7 = new KisTransparencyMask(); + tmask7->setName("tmask7"); + + KisPaintLayerSP paint8 = new KisPaintLayer(t.image, "paint8", OPACITY_OPAQUE_U8); + + KisSelectionMaskSP selection9 = new KisSelectionMask(t.image); + selection9->setName("selection9"); + + KisTransparencyMaskSP tmask10 = new KisTransparencyMask(); + tmask10->setName("tmask10"); + + + t.image->addNode(group1, t.image->root(), t.findBlur1()); + + t.image->addNode(paint2, group1); + t.image->addNode(selection3, paint2); + + t.image->addNode(paint4, group1); + t.image->addNode(tmask5, paint4); + t.image->addNode(selection6, paint4); + t.image->addNode(tmask7, paint4); + + t.image->addNode(paint8, group1); + t.image->addNode(selection9, paint8); + t.image->addNode(tmask10, paint8); + + //checkNode(t.image->root(), ""); + + qDebug() << "== Nodes"; + printNodes(t.image->root()); + + { + qDebug() << "== Leafs backward"; + + QList refNodes; + refNodes << "root" + << "paint1" + << "tmask1" + << "group1" + << "paint8" << "tmask10" + << "paint4" << "tmask7" << "tmask5" + << "paint2" + << "blur1" + << "clone1"; + + printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); + } + + { + qDebug() << "== Leafs forward"; + + QList refNodes; + refNodes << "root" + << "clone1" + << "blur1" + << "group1" + << "paint2" + << "paint4" << "tmask5" << "tmask7" + << "paint8" << "tmask10" + << "paint1" + << "tmask1"; + + printLeafsForward(t.image->root()->projectionLeaf(), refNodes); + } + + { + qDebug() << "== Parents for tmask5"; + QList refNodes; + refNodes << "tmask5" << "paint4" << "group1" << "root"; + printParents(tmask5->projectionLeaf(), refNodes); + } + + { + qDebug() << "== Parents for selection6"; + QList refNodes; + refNodes << "selection6"; + printParents(selection6->projectionLeaf(), refNodes); + } + + + /** + * Selection masks are just excluded from the entire rendering hierarchy + */ + QVERIFY(!selection6->projectionLeaf()->nextSibling()); + QVERIFY(!selection6->projectionLeaf()->prevSibling()); +} + +void KisProjectionLeafTest::testSelectionMaskOverlay() +{ + TestImage t; + + KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); + KisPaintLayerSP paint2 = new KisPaintLayer(t.image, "paint2", OPACITY_OPAQUE_U8); + KisSelectionMaskSP selection3 = new KisSelectionMask(t.image); + selection3->setName("selection3"); + selection3->setSelection(new KisSelection(new KisSelectionDefaultBounds(paint2->paintDevice(), t.image))); + + t.image->addNode(group1, t.image->root(), t.findBlur1()); + + t.image->addNode(paint2, group1); + t.image->addNode(selection3, paint2); + + t.image->setOverlaySelectionMask(selection3); + + t.image->waitForDone(); + + + qDebug() << "== Nodes"; + printNodes(t.image->root()); + + { + qDebug() << "== Leafs backward"; + + QList refNodes; + refNodes << "root" + << "selection3" + << "paint1" + << "tmask1" + << "group1" + << "paint2" + << "blur1" + << "clone1"; + + printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); + } + + { + qDebug() << "== Leafs forward"; + + QList refNodes; + refNodes << "root" + << "clone1" + << "blur1" + << "group1" + << "paint2" + << "paint1" + << "tmask1" + << "selection3"; + + printLeafsForward(t.image->root()->projectionLeaf(), refNodes); + } + + { + qDebug() << "== Parents for selection3"; + QList refNodes; + refNodes << "selection3" << "root"; + printParents(selection3->projectionLeaf(), refNodes); + } +} + QTEST_MAIN(KisProjectionLeafTest) diff --git a/libs/image/tests/kis_projection_leaf_test.h b/libs/image/tests/kis_projection_leaf_test.h index 7474737713..0ead4f8967 100644 --- a/libs/image/tests/kis_projection_leaf_test.h +++ b/libs/image/tests/kis_projection_leaf_test.h @@ -1,33 +1,36 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PROJECTION_LEAF_TEST_H #define __KIS_PROJECTION_LEAF_TEST_H #include class KisProjectionLeafTest : public QObject { Q_OBJECT private Q_SLOTS: void test(); void testPassThrough(); void testNestedPassThrough(); + void testSkippedSelectionMasks(); + + void testSelectionMaskOverlay(); }; #endif /* __KIS_PROJECTION_LEAF_TEST_H */ diff --git a/libs/image/tiles3/KisTiledExtentManager.cpp b/libs/image/tiles3/KisTiledExtentManager.cpp index 846a8b12f5..95de72911f 100644 --- a/libs/image/tiles3/KisTiledExtentManager.cpp +++ b/libs/image/tiles3/KisTiledExtentManager.cpp @@ -1,348 +1,320 @@ /* * Copyright (c) 2017 Dmitry Kazakov * Copyright (c) 2018 Andrey Kamakin * * 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 "KisTiledExtentManager.h" #include #include #include "kis_tile_data_interface.h" #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" KisTiledExtentManager::Data::Data() : m_min(qint32_MAX), m_max(qint32_MIN), m_count(0) { QWriteLocker lock(&m_migrationLock); m_capacity = InitialBufferSize; - m_offset = 0; + m_offset = 1; m_buffer = new QAtomicInt[m_capacity]; } KisTiledExtentManager::Data::~Data() { QWriteLocker lock(&m_migrationLock); delete[] m_buffer; } inline bool KisTiledExtentManager::Data::add(qint32 index) { QReadLocker lock(&m_migrationLock); qint32 currentIndex = m_offset + index; if (currentIndex < 0 || currentIndex >= m_capacity) { lock.unlock(); migrate(index); lock.relock(); currentIndex = m_offset + index; } KIS_ASSERT_RECOVER_NOOP(m_buffer[currentIndex].loadAcquire() >= 0); bool needsUpdateExtent = false; QReadLocker rl(&m_extentLock); if (!m_buffer[currentIndex].loadAcquire()) { rl.unlock(); QWriteLocker wl(&m_extentLock); if (!m_buffer[currentIndex].load()) { m_buffer[currentIndex].store(1); if (m_min > index) m_min = index; if (m_max < index) m_max = index; ++m_count; needsUpdateExtent = true; } else { m_buffer[currentIndex].ref(); } } else { m_buffer[currentIndex].ref(); } return needsUpdateExtent; } inline bool KisTiledExtentManager::Data::remove(qint32 index) { QReadLocker lock(&m_migrationLock); qint32 currentIndex = m_offset + index; - if (currentIndex < 0 || currentIndex >= m_capacity) { - lock.unlock(); - migrate(index); - lock.relock(); - currentIndex = m_offset + index; - } - KIS_ASSERT_RECOVER_NOOP(m_buffer[currentIndex].loadAcquire() > 0); bool needsUpdateExtent = false; QReadLocker rl(&m_extentLock); if (m_buffer[currentIndex].loadAcquire() == 1) { rl.unlock(); QWriteLocker wl(&m_extentLock); if (m_buffer[currentIndex].load() == 1) { m_buffer[currentIndex].store(0); if (m_min == index) updateMin(); if (m_max == index) updateMax(); --m_count; needsUpdateExtent = true; } else { KIS_ASSERT_RECOVER_NOOP(0 && "sanity check failed: the tile has already been removed!"); } } else { m_buffer[currentIndex].deref(); } return needsUpdateExtent; } void KisTiledExtentManager::Data::replace(const QVector &indexes) { QWriteLocker lock(&m_migrationLock); QWriteLocker l(&m_extentLock); for (qint32 i = 0; i < m_capacity; ++i) { m_buffer[i].store(0); } m_min = qint32_MAX; m_max = qint32_MIN; m_count = 0; Q_FOREACH (const qint32 index, indexes) { unsafeAdd(index); } } void KisTiledExtentManager::Data::clear() { QWriteLocker lock(&m_migrationLock); QWriteLocker l(&m_extentLock); for (qint32 i = 0; i < m_capacity; ++i) { m_buffer[i].store(0); } m_min = qint32_MAX; m_max = qint32_MIN; m_count = 0; } bool KisTiledExtentManager::Data::isEmpty() { return m_count == 0; } qint32 KisTiledExtentManager::Data::min() { return m_min; } qint32 KisTiledExtentManager::Data::max() { return m_max; } void KisTiledExtentManager::Data::unsafeAdd(qint32 index) { qint32 currentIndex = m_offset + index; if (currentIndex < 0 || currentIndex >= m_capacity) { unsafeMigrate(index); currentIndex = m_offset + index; } if (!m_buffer[currentIndex].fetchAndAddRelaxed(1)) { if (m_min > index) m_min = index; if (m_max < index) m_max = index; ++m_count; } } void KisTiledExtentManager::Data::unsafeMigrate(qint32 index) { qint32 oldCapacity = m_capacity; + qint32 oldOffset = m_offset; qint32 currentIndex = m_offset + index; - auto reallocFunc = [&](qint32 start) { + while (currentIndex < 0 || currentIndex >= m_capacity) { + m_capacity <<= 1; + + if (currentIndex < 0) { + m_offset <<= 1; + currentIndex = m_offset + index; + } + } + + if (m_capacity != oldCapacity) { QAtomicInt *newBuffer = new QAtomicInt[m_capacity]; + qint32 start = m_offset - oldOffset; for (qint32 i = 0; i < oldCapacity; ++i) { newBuffer[start + i].store(m_buffer[i].load()); } delete[] m_buffer; m_buffer = newBuffer; - }; - - if (currentIndex < 0) { - qint32 oldOffset = m_offset; - m_offset = -index; - qint32 start = m_offset - oldOffset; - qint32 count = m_max - m_min + 1; - qint32 capacity = m_offset + start + count; - - while (capacity >= m_capacity) { - m_capacity <<= 1; - } - - if (m_capacity != oldCapacity) { - reallocFunc(start); - } else { - for (qint32 i = count; i >= 0; --i) { - m_buffer[start + i].store(m_buffer[i].load()); - } - - for (qint32 i = 0; i < start; ++i) { - m_buffer[i].store(0); - } - } - } else { - while (currentIndex >= m_capacity) { - m_capacity <<= 1; - } - - if (m_capacity != oldCapacity) { - reallocFunc(0); - } } } void KisTiledExtentManager::Data::migrate(qint32 index) { QWriteLocker lock(&m_migrationLock); unsafeMigrate(index); } void KisTiledExtentManager::Data::updateMin() { qint32 start = m_min + m_offset + 1; for (qint32 i = start; i < m_capacity; ++i) { qint32 current = m_buffer[i].load(); if (current > 0) { m_min = current; break; } } } void KisTiledExtentManager::Data::updateMax() { qint32 start = m_max + m_offset - 1; for (qint32 i = start; i >= 0; --i) { qint32 current = m_buffer[i].load(); if (current > 0) { m_max = current; break; } } } KisTiledExtentManager::KisTiledExtentManager() { } void KisTiledExtentManager::notifyTileAdded(qint32 col, qint32 row) { bool needsUpdateExtent = false; needsUpdateExtent |= m_colsData.add(col); needsUpdateExtent |= m_rowsData.add(row); if (needsUpdateExtent) { updateExtent(); } } void KisTiledExtentManager::notifyTileRemoved(qint32 col, qint32 row) { bool needsUpdateExtent = false; needsUpdateExtent |= m_colsData.remove(col); needsUpdateExtent |= m_rowsData.remove(row); if (needsUpdateExtent) { updateExtent(); } } void KisTiledExtentManager::replaceTileStats(const QVector &indexes) { QVector colsIndexes; QVector rowsIndexes; Q_FOREACH (const QPoint &index, indexes) { colsIndexes.append(index.x()); rowsIndexes.append(index.y()); } m_colsData.replace(colsIndexes); m_rowsData.replace(rowsIndexes); updateExtent(); } void KisTiledExtentManager::clear() { m_colsData.clear(); m_rowsData.clear(); QWriteLocker lock(&m_extentLock); m_currentExtent = QRect(qint32_MAX, qint32_MAX, 0, 0); } QRect KisTiledExtentManager::extent() const { QReadLocker lock(&m_extentLock); return m_currentExtent; } void KisTiledExtentManager::updateExtent() { QReadLocker cl(&m_colsData.m_extentLock); QReadLocker rl(&m_rowsData.m_extentLock); bool colsEmpty = m_colsData.isEmpty(); bool rowsEmpty = m_rowsData.isEmpty(); KIS_ASSERT_RECOVER_RETURN(colsEmpty == rowsEmpty); if (colsEmpty && rowsEmpty) { QWriteLocker lock(&m_extentLock); m_currentExtent = QRect(qint32_MAX, qint32_MAX, 0, 0); } else { const qint32 minX = m_colsData.min() * KisTileData::WIDTH; const qint32 maxPlusOneX = (m_colsData.max() + 1) * KisTileData::WIDTH; const qint32 minY = m_rowsData.min() * KisTileData::HEIGHT; const qint32 maxPlusOneY = (m_rowsData.max() + 1) * KisTileData::HEIGHT; QWriteLocker lock(&m_extentLock); m_currentExtent = QRect(minX, minY, maxPlusOneX - minX, maxPlusOneY - minY); } } diff --git a/libs/libkis/tests/TestDocument.cpp b/libs/libkis/tests/TestDocument.cpp index bdf3c14c50..d6736dc250 100644 --- a/libs/libkis/tests/TestDocument.cpp +++ b/libs/libkis/tests/TestDocument.cpp @@ -1,214 +1,221 @@ /* Copyright (C) 2017 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestDocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + void TestDocument::testSetColorSpace() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 255); image->addNode(layer); kisdoc->setCurrentImage(image); Document d(kisdoc); QStringList profiles = Krita().profiles("GRAYA", "U16"); d.setColorSpace("GRAYA", "U16", profiles.first()); QVERIFY(layer->colorSpace()->colorModelId().id() == "GRAYA"); QVERIFY(layer->colorSpace()->colorDepthId().id() == "U16"); QVERIFY(layer->colorSpace()->profile()->name() == "gray built-in"); + + KisPart::instance()->removeDocument(kisdoc); } void TestDocument::testSetColorProfile() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 255); image->addNode(layer); kisdoc->setCurrentImage(image); Document d(kisdoc); QStringList profiles = Krita().profiles("RGBA", "U8"); Q_FOREACH(const QString &profile, profiles) { d.setColorProfile(profile); QVERIFY(image->colorSpace()->profile()->name() == profile); } + KisPart::instance()->removeDocument(kisdoc); } void TestDocument::testPixelData() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 255); KisFillPainter gc(layer->paintDevice()); gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace())); image->addNode(layer); kisdoc->setCurrentImage(image); Document d(kisdoc); d.refreshProjection(); QByteArray ba = d.pixelData(0, 0, 100, 100); QDataStream ds(ba); do { quint8 channelvalue; ds >> channelvalue; QVERIFY(channelvalue == 0); ds >> channelvalue; QVERIFY(channelvalue == 0); ds >> channelvalue; QVERIFY(channelvalue == 255); ds >> channelvalue; QVERIFY(channelvalue == 255); } while (!ds.atEnd()); + + KisPart::instance()->removeDocument(kisdoc); } void TestDocument::testThumbnail() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 255); KisFillPainter gc(layer->paintDevice()); gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace())); image->addNode(layer); kisdoc->setCurrentImage(image); Document d(kisdoc); d.refreshProjection(); QImage thumb = d.thumbnail(10, 10); thumb.save("thumb.png"); QVERIFY(thumb.width() == 10); QVERIFY(thumb.height() == 10); // Our thumbnail calculator in KisPaintDevice cannot make a filled 10x10 thumbnail from a 100x100 device, // it makes it 10x10 empty, then puts 8x8 pixels in there... Not a bug in the Node class for (int i = 0; i < 8; ++i) { for (int j = 0; j < 8; ++j) { #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) QVERIFY(thumb.pixelColor(i, j) == QColor(Qt::red)); #else QVERIFY(QColor(thumb.pixel(i, j)) == QColor(Qt::red)); #endif } } + KisPart::instance()->removeDocument(kisdoc); } void TestDocument::testCreateAndSave() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 5000, 5000, KoColorSpaceRegistry::instance()->rgb16(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 128); KisFillPainter gc(layer->paintDevice()); gc.fillRect(100, 100, 4000, 4000, KoColor(Qt::red, layer->colorSpace())); image->addNode(layer); for(int i = 0; i < 10; ++i) { image->addNode(layer->clone()); } kisdoc->setCurrentImage(image); Document d(kisdoc); d.setBatchmode(true); d.refreshProjection(); QString filename = QDir::tempPath() + "/TestDocumentTestCreateAndSave.kra"; d.saveAs(filename); QVERIFY2(QFileInfo(filename).exists(), filename.toUtf8()); Document *d2 = Krita::instance()->openDocument(filename); Q_ASSERT(d2->colorDepth() == "U16"); - delete kisdoc; - delete d2; + KisPart::instance()->removeDocument(kisdoc); } void TestDocument::testCreateFillLayer() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 50, 50, KoColorSpaceRegistry::instance()->rgb16(), "test"); kisdoc->setCurrentImage(image); Document d(kisdoc); const QString pattern("pattern"); const QString color("color"); const QString filllayer = "filllayer"; InfoObject info; Selection sel(image->globalSelection()); FillLayer *f = d.createFillLayer("test1", pattern, info, sel); QVERIFY(f->generatorName() == pattern); QVERIFY(f->type() == filllayer); delete f; f = d.createFillLayer("test1", color, info, sel); QVERIFY(f->generatorName() == color); QVERIFY(f->type() == filllayer); info.setProperty(pattern, "Cross01.pat"); QVERIFY(f->setGenerator(pattern, &info)); QVERIFY(f->filterConfig()->property(pattern).toString() == "Cross01.pat"); QVERIFY(f->generatorName() == pattern); QVERIFY(f->type() == filllayer); info.setProperty(color, QColor(Qt::red)); QVERIFY(f->setGenerator(color, &info)); QVariant v = f->filterConfig()->property(color); QColor c = v.value(); QVERIFY(c == QColor(Qt::red)); QVERIFY(f->generatorName() == color); QVERIFY(f->type() == filllayer); bool r = f->setGenerator(QString("xxx"), &info); QVERIFY(!r); delete f; QVERIFY(d.createFillLayer("test1", "xxx", info, sel) == 0); - delete kisdoc; + KisPart::instance()->removeDocument(kisdoc); } -QTEST_MAIN(TestDocument) +KISTEST_MAIN(TestDocument) diff --git a/libs/libkis/tests/TestFilter.cpp b/libs/libkis/tests/TestFilter.cpp index 2a3f9861ca..d385749aca 100644 --- a/libs/libkis/tests/TestFilter.cpp +++ b/libs/libkis/tests/TestFilter.cpp @@ -1,102 +1,104 @@ /* Copyright (C) 2017 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + void TestFilter::testApply() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 255); KisFillPainter gc(layer->paintDevice()); gc.fillRect(0, 0, 100, 100, KoColor(Qt::black, layer->colorSpace())); image->addNode(layer); kisdoc->setCurrentImage(image); Document d(kisdoc); Node node(image, layer); Filter f; f.setName("invert"); QVERIFY(f.configuration()); d.lock(); f.apply(&node, 0, 0, 100, 100); d.unlock(); d.refreshProjection(); for (int i = 0; i < 100 ; i++) { for (int j = 0; j < 100 ; j++) { QColor pixel; layer->paintDevice()->pixel(i, j, &pixel); QVERIFY(pixel == QColor(Qt::white)); } } } void TestFilter::testStartFilter() { KisDocument *kisdoc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test"); KisNodeSP layer = new KisPaintLayer(image, "test1", 255); KisFillPainter gc(layer->paintDevice()); gc.fillRect(0, 0, 100, 100, KoColor(Qt::black, layer->colorSpace())); image->addNode(layer); kisdoc->setCurrentImage(image); Document d(kisdoc); Node node(image, layer); Filter f; f.setName("invert"); QVERIFY(f.configuration()); f.startFilter(&node, 0, 0, 100, 100); image->waitForDone(); for (int i = 0; i < 100 ; i++) { for (int j = 0; j < 100 ; j++) { QColor pixel; layer->paintDevice()->pixel(i, j, &pixel); QVERIFY(pixel == QColor(Qt::white)); } } } -QTEST_MAIN(TestFilter) +KISTEST_MAIN(TestFilter) diff --git a/libs/libkis/tests/TestNotifier.cpp b/libs/libkis/tests/TestNotifier.cpp index 98cac2780b..f0b3fbf04e 100644 --- a/libs/libkis/tests/TestNotifier.cpp +++ b/libs/libkis/tests/TestNotifier.cpp @@ -1,51 +1,53 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestNotifier.h" #include #include #include #include +#include + void TestNotifier::testNotifier() { KisPart *part = KisPart::instance(); Notifier *notifier = new Notifier(); connect(notifier, SIGNAL(imageCreated(Document*)), SLOT(documentAdded(Document*)), Qt::DirectConnection); QVERIFY(!notifier->active()); notifier->setActive(true); QVERIFY(notifier->active()); KisDocument *doc = part->createDocument(); part->addDocument(doc); QVERIFY(m_document); } void TestNotifier::documentAdded(Document *image) { m_document = image; } -QTEST_MAIN(TestNotifier) +KISTEST_MAIN(TestNotifier) diff --git a/libs/odf/KoColumns.cpp b/libs/odf/KoColumns.cpp index 8a57573060..6307056ca5 100644 --- a/libs/odf/KoColumns.cpp +++ b/libs/odf/KoColumns.cpp @@ -1,283 +1,283 @@ /* This file is part of the KDE project Copyright 2012 Friedrich W. H. Kossebau 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 "KoColumns.h" #include #include "KoXmlWriter.h" #include "KoGenStyle.h" #include "KoXmlReader.h" #include "KoXmlNS.h" #include "KoUnit.h" // KDE #include static const int defaultColumnCount = 1; static const KoColumns::SeparatorStyle defaultSeparatorStyle = KoColumns::None; static const int defaultSeparatorHeight = 100; static const Qt::GlobalColor defaultSeparatorColor = Qt::black; static const KoColumns::SeparatorVerticalAlignment defaultSeparatorVerticalAlignment = KoColumns::AlignTop; static const int defaultColumnGapWidth = 17; // in pt, ~ 6mm const char * KoColumns::separatorStyleString(KoColumns::SeparatorStyle separatorStyle) { const char * result; // skip KoColumns::None, is default if (separatorStyle == Solid) { result = "solid"; } else if (separatorStyle == Dotted) { result = "dotted"; } else if (separatorStyle == Dashed) { result = "dashed"; } else if (separatorStyle == DotDashed) { result = "dot-dashed"; } else { result = "none"; } return result; } const char * KoColumns::separatorVerticalAlignmentString(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment) { const char * result; // skip KoColumns::AlignTop, is default if (separatorVerticalAlignment == AlignVCenter) { result = "middle"; } else if (separatorVerticalAlignment == AlignBottom) { result = "bottom"; } else { result = "top"; } return result; } KoColumns::SeparatorVerticalAlignment KoColumns::parseSeparatorVerticalAlignment(const QString &value) { // default to AlignTop SeparatorVerticalAlignment result = defaultSeparatorVerticalAlignment; if (! value.isEmpty()) { // skip "top", is default if (value == QLatin1String("middle")) { result = AlignVCenter; } else if (value == QLatin1String("bottom")) { result = AlignBottom; } } return result; } QColor KoColumns::parseSeparatorColor(const QString &value) { QColor result(value); if (! result.isValid()) // default is black, cmp. ODF 1.2 §19.467 result = QColor(defaultSeparatorColor); return result; } // TODO: see if there is another parse method somewhere which can be used here int KoColumns::parseSeparatorHeight(const QString &value) { int result = defaultSeparatorHeight; // only try to convert if it ends with a %, so is also not empty if (value.endsWith(QLatin1Char('%'))) { bool ok = false; // try to convert result = value.left(value.length()-1).toInt(&ok); // reset to 100% if conversion failed (which sets result to 0) if (! ok) { result = defaultSeparatorHeight; } } return result; } KoColumns::SeparatorStyle KoColumns::parseSeparatorStyle(const QString &value) { SeparatorStyle result = None; if (! value.isEmpty()) { // skip "none", is default if (value == QLatin1String("solid")) { result = Solid; } else if (value == QLatin1String("dotted")) { result = Dotted; } else if (value == QLatin1String("dashed")) { result = Dashed; } else if (value == QLatin1String("dot-dashed")) { result = DotDashed; } } return result; } int KoColumns::parseRelativeWidth(const QString &value) { int result = 0; // only try to convert if it ends with a *, so is also not empty if (value.endsWith(QLatin1Char('*'))) { bool ok = false; // try to convert result = value.left(value.length()-1).toInt(&ok); if (! ok) { result = 0; } } return result; } KoColumns::KoColumns() : count(defaultColumnCount) , gapWidth(defaultColumnGapWidth) , separatorStyle(defaultSeparatorStyle) , separatorColor(defaultSeparatorColor) , separatorVerticalAlignment(defaultSeparatorVerticalAlignment) , separatorHeight(defaultSeparatorHeight) { } void KoColumns::reset() { count = defaultColumnCount; gapWidth = defaultColumnGapWidth; separatorStyle = defaultSeparatorStyle; separatorColor = QColor(defaultSeparatorColor); separatorVerticalAlignment = defaultSeparatorVerticalAlignment; separatorHeight = defaultSeparatorHeight; } bool KoColumns::operator==(const KoColumns& rhs) const { return count == rhs.count && (columnData.isEmpty() && rhs.columnData.isEmpty() ? (qAbs(gapWidth - rhs.gapWidth) <= 1E-10) : (columnData == rhs.columnData)); } bool KoColumns::operator!=(const KoColumns& rhs) const { return count != rhs.count || (columnData.isEmpty() && rhs.columnData.isEmpty() ? qAbs(gapWidth - rhs.gapWidth) > 1E-10 : ! (columnData == rhs.columnData)); } void KoColumns::loadOdf(const KoXmlElement &style) { KoXmlElement columnsElement = KoXml::namedItemNS(style, KoXmlNS::style, "columns"); if (!columnsElement.isNull()) { count = columnsElement.attributeNS(KoXmlNS::fo, "column-count").toInt(); if (count < 1) count = 1; gapWidth = KoUnit::parseValue(columnsElement.attributeNS(KoXmlNS::fo, "column-gap")); KoXmlElement columnSep = KoXml::namedItemNS(columnsElement, KoXmlNS::style, "column-sep"); if (! columnSep.isNull()) { separatorStyle = parseSeparatorStyle(columnSep.attributeNS(KoXmlNS::style, "style")); separatorWidth = KoUnit::parseValue(columnSep.attributeNS(KoXmlNS::style, "width")); separatorHeight = parseSeparatorHeight(columnSep.attributeNS(KoXmlNS::style, "height")); separatorColor = parseSeparatorColor(columnSep.attributeNS(KoXmlNS::style, "color")); separatorVerticalAlignment = parseSeparatorVerticalAlignment(columnSep.attributeNS(KoXmlNS::style, "vertical-align")); } KoXmlElement columnElement; forEachElement(columnElement, columnsElement) { if(columnElement.localName() != QLatin1String("column") || columnElement.namespaceURI() != KoXmlNS::style) continue; ColumnDatum datum; datum.leftMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "start-indent"), 0.0); datum.rightMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "end-indent"), 0.0); datum.topMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-before"), 0.0); datum.bottomMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-after"), 0.0); datum.relativeWidth = parseRelativeWidth(columnElement.attributeNS(KoXmlNS::style, "rel-width")); // on a bad relativeWidth just drop all data if (datum.relativeWidth <= 0) { columnData.clear(); break; } columnData.append(datum); } if (! columnData.isEmpty() && count != columnData.count()) { warnOdf << "Found not as many elements as attribute fo:column-count has set:"<< count; columnData.clear(); } } else { reset(); } } void KoColumns::saveOdf(KoGenStyle &style) const { if (count > 1) { QBuffer buffer; buffer.open(QIODevice::WriteOnly); KoXmlWriter writer(&buffer); writer.startElement("style:columns"); writer.addAttribute("fo:column-count", count); if (columnData.isEmpty()) { - writer.addAttributePt("fo:column-gap", gapWidth); + writer.addAttribute("fo:column-gap", gapWidth); } if (separatorStyle != KoColumns::None) { writer.startElement("style:column-sep"); writer.addAttribute("style:style", separatorStyleString(separatorStyle)); - writer.addAttributePt("style:width", separatorWidth); + writer.addAttribute("style:width", separatorWidth); writer.addAttribute("style:height", QString::number(separatorHeight)+QLatin1Char('%')); writer.addAttribute("style:color", separatorColor.name()); writer.addAttribute("style:vertical-align", separatorVerticalAlignmentString(separatorVerticalAlignment)); writer.endElement(); // style:column-sep } Q_FOREACH (const ColumnDatum &cd, columnData) { writer.startElement("style:column"); - writer.addAttributePt("fo:start-indent", cd.leftMargin); - writer.addAttributePt("fo:end-indent", cd.rightMargin); - writer.addAttributePt("fo:space-before", cd.topMargin); - writer.addAttributePt("fo:space-after", cd.bottomMargin); + writer.addAttribute("fo:start-indent", cd.leftMargin); + writer.addAttribute("fo:end-indent", cd.rightMargin); + writer.addAttribute("fo:space-before", cd.topMargin); + writer.addAttribute("fo:space-after", cd.bottomMargin); writer.addAttribute("style:rel-width", QString::number(cd.relativeWidth)+QLatin1Char('*')); writer.endElement(); // style:column } writer.endElement(); // style:columns QString contentElement = QString::fromUtf8(buffer.buffer(), buffer.buffer().size()); style.addChildElement("style:columns", contentElement); } } diff --git a/libs/odf/KoGenStyle.cpp b/libs/odf/KoGenStyle.cpp index f9b9fbff0a..d26f9cacce 100644 --- a/libs/odf/KoGenStyle.cpp +++ b/libs/odf/KoGenStyle.cpp @@ -1,523 +1,523 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2010 Jarosław Staniek Copyright (C) 2011 Pierre Ducroquet 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 "KoGenStyle.h" #include "KoGenStyles.h" #include #include #include #include // Returns -1, 0 (equal) or 1 static int compareMap(const QMap& map1, const QMap& map2) { QMap::const_iterator it = map1.constBegin(); QMap::const_iterator oit = map2.constBegin(); for (; it != map1.constEnd(); ++it, ++oit) { // both maps have been checked for size already if (it.key() != oit.key()) return it.key() < oit.key() ? -1 : + 1; if (it.value() != oit.value()) return it.value() < oit.value() ? -1 : + 1; } return 0; // equal } KoGenStyle::KoGenStyle(Type type, const char* familyName, const QString& parentName) : m_type(type), m_familyName(familyName), m_parentName(parentName), m_autoStyleInStylesDotXml(false), m_defaultStyle(false) { switch (type) { case TextStyle: case TextAutoStyle: m_propertyType = TextType; break; case ParagraphStyle: case ParagraphAutoStyle: m_propertyType = ParagraphType; break; case GraphicStyle: case GraphicAutoStyle: m_propertyType = GraphicType; break; case SectionStyle: case SectionAutoStyle: m_propertyType = SectionType; break; case RubyStyle: case RubyAutoStyle: m_propertyType = RubyType; break; case TableStyle: case TableAutoStyle: m_propertyType = TableType; break; case TableColumnStyle: case TableColumnAutoStyle: m_propertyType = TableColumnType; break; case TableRowStyle: case TableRowAutoStyle: m_propertyType = TableRowType; break; case TableCellStyle: case TableCellAutoStyle: m_propertyType = TableCellType; break; case PresentationStyle: case PresentationAutoStyle: m_propertyType = PresentationType; break; case DrawingPageStyle: case DrawingPageAutoStyle: m_propertyType = DrawingPageType; break; case ChartStyle: case ChartAutoStyle: m_propertyType = ChartType; break; default: m_propertyType = DefaultType; break; } } KoGenStyle::~KoGenStyle() { } /* * The order of this list is important; e.g. a graphic-properties must * precede a text-properties always. See the Relax NG to check the order. */ static const KoGenStyle::PropertyType s_propertyTypes[] = { KoGenStyle::DefaultType, KoGenStyle::SectionType, KoGenStyle::RubyType, KoGenStyle::TableType, KoGenStyle::TableColumnType, KoGenStyle::TableRowType, KoGenStyle::TableCellType, KoGenStyle::DrawingPageType, KoGenStyle::ChartType, KoGenStyle::GraphicType, KoGenStyle::ParagraphType, KoGenStyle::TextType, }; static const char* const s_propertyNames[] = { 0, "style:section-properties", "style:ruby-properties", "style:table-properties", "style:table-column-properties", "style:table-row-properties", "style:table-cell-properties", "style:drawing-page-properties", "style:chart-properties", "style:graphic-properties", "style:paragraph-properties", "style:text-properties" }; static const int s_propertyNamesCount = sizeof(s_propertyNames) / sizeof(*s_propertyNames); static KoGenStyle::PropertyType propertyTypeByElementName(const char* propertiesElementName) { for (int i = 0; i < s_propertyNamesCount; ++i) { if (qstrcmp(s_propertyNames[i], propertiesElementName) == 0) { return s_propertyTypes[i]; } } return KoGenStyle::DefaultType; } void KoGenStyle::writeStyleProperties(KoXmlWriter* writer, PropertyType type, const KoGenStyle* parentStyle) const { const char* elementName = 0; for (int i=0; istartElement(elementName); QMap::const_iterator it = map.constBegin(); const QMap::const_iterator end = map.constEnd(); for (; it != end; ++it) { if (!parentStyle || parentStyle->property(it.key(), type) != it.value()) writer->addAttribute(it.key().toUtf8(), it.value().toUtf8()); } QMap::const_iterator itChild = mapChild.constBegin(); const QMap::const_iterator endChild = mapChild.constEnd(); for (; itChild != endChild; ++itChild) { if (!parentStyle || parentStyle->childProperty(itChild.key(), type) != itChild.value()) writer->addCompleteElement(itChild.value().toUtf8()); } writer->endElement(); } } void KoGenStyle::writeStyle(KoXmlWriter* writer, const KoGenStyles& styles, const char* elementName, const QString& name, const char* propertiesElementName, bool closeElement, bool drawElement) const { //debugOdf <<"writing out style" << name <<" display-name=" << m_attributes["style:display-name"] <<" family=" << m_familyName; writer->startElement(elementName); const KoGenStyle* parentStyle = 0; if (!m_defaultStyle) { if (!drawElement) writer->addAttribute("style:name", name); else writer->addAttribute("draw:name", name); if (!m_parentName.isEmpty()) { Q_ASSERT(!m_familyName.isEmpty()); parentStyle = styles.style(m_parentName, m_familyName); if (parentStyle && m_familyName.isEmpty()) { // get family from parent style, just in case // Note: this is saving code, don't convert to attributeNS! const_cast(this)-> m_familyName = parentStyle->attribute("style:family").toLatin1(); //debugOdf <<"Got familyname" << m_familyName <<" from parent"; } if (parentStyle && !parentStyle->isDefaultStyle()) writer->addAttribute("style:parent-style-name", m_parentName); } } else { // default-style Q_ASSERT(qstrcmp(elementName, "style:default-style") == 0); Q_ASSERT(m_parentName.isEmpty()); } if (!m_familyName.isEmpty()) const_cast(this)-> addAttribute("style:family", QString::fromLatin1(m_familyName)); else { if (qstrcmp(elementName, "style:style") == 0) warnOdf << "User style " << name << " is without family - invalid. m_type=" << m_type; } #if 0 // #ifndef NDEBUG debugOdf << "style:" << name; printDebug(); if (parentStyle) { debugOdf << " parent:" << m_parentName; parentStyle->printDebug(); } #endif // Write attributes [which differ from the parent style] // We only look at the direct parent style because we assume // that styles are fully specified, i.e. the inheritance is // only in the final file, not in the caller's code. QMap::const_iterator it = m_attributes.constBegin(); for (; it != m_attributes.constEnd(); ++it) { bool writeit = true; if (parentStyle && it.key() != "style:family" // always write the family out && parentStyle->attribute(it.key()) == it.value()) writeit = false; if (writeit) writer->addAttribute(it.key().toUtf8(), it.value().toUtf8()); } bool createPropertiesTag = propertiesElementName && propertiesElementName[0] != '\0'; KoGenStyle::PropertyType i = KoGenStyle::DefaultType; KoGenStyle::PropertyType defaultPropertyType = KoGenStyle::DefaultType; if (createPropertiesTag) defaultPropertyType = propertyTypeByElementName(propertiesElementName); if (!m_properties[i].isEmpty() || !m_childProperties[defaultPropertyType].isEmpty() || !m_properties[defaultPropertyType].isEmpty()) { if (createPropertiesTag) writer->startElement(propertiesElementName); // e.g. paragraph-properties it = m_properties[i].constBegin(); for (; it != m_properties[i].constEnd(); ++it) { if (!parentStyle || parentStyle->property(it.key(), i) != it.value()) writer->addAttribute(it.key().toUtf8(), it.value().toUtf8()); } //write the explicitly-defined properties that are the same type as the default, //but only if defaultPropertyType is Text, Paragraph, or GraphicType if (defaultPropertyType != 0) { it = m_properties[defaultPropertyType].constBegin(); for (; it != m_properties[defaultPropertyType].constEnd(); ++it) { if (!parentStyle || parentStyle->property(it .key(), defaultPropertyType) != it.value()) writer->addAttribute(it.key().toUtf8(), it.value().toUtf8()); } } //write child elements of the properties elements it = m_childProperties[defaultPropertyType].constBegin(); for (; it != m_childProperties[defaultPropertyType].constEnd(); ++it) { if (!parentStyle || parentStyle->childProperty(it.key(), defaultPropertyType) != it.value()) { writer->addCompleteElement(it.value().toUtf8()); } } if (createPropertiesTag) writer->endElement(); } // now write out any other properties elements //start with i=1 to skip the defaultType that we already took care of for (int i = 1; i < s_propertyNamesCount; ++i) { //skip any properties that are the same as the defaultType if (s_propertyTypes[i] != defaultPropertyType) { writeStyleProperties(writer, s_propertyTypes[i], parentStyle); } } //write child elements that aren't in any of the properties elements i = KoGenStyle::StyleChildElement; it = m_properties[i].constBegin(); for (; it != m_properties[i].constEnd(); ++it) { if (!parentStyle || parentStyle->property(it.key(), i) != it.value()) { writer->addCompleteElement(it.value().toUtf8()); } } // And now the style maps for (int i = 0; i < m_maps.count(); ++i) { bool writeit = true; if (parentStyle && compareMap(m_maps[i], parentStyle->m_maps[i]) == 0) writeit = false; if (writeit) { writer->startElement("style:map"); QMap::const_iterator it = m_maps[i].constBegin(); for (; it != m_maps[i].constEnd(); ++it) { writer->addAttribute(it.key().toUtf8(), it.value().toUtf8()); } writer->endElement(); // style:map } } if (closeElement) writer->endElement(); } void KoGenStyle::addPropertyPt(const QString& propName, qreal propValue, PropertyType type) { if (type == DefaultType) { type = m_propertyType; } QString str; str.setNum(propValue, 'f', DBL_DIG); str += "pt"; m_properties[type].insert(propName, str); } void KoGenStyle::addPropertyLength(const QString& propName, const QTextLength &propValue, PropertyType type) { if (type == DefaultType) { type = m_propertyType; } if (propValue.type() == QTextLength::FixedLength) { return addPropertyPt(propName, propValue.rawValue(), type); } else { QString str; str.setNum((int) propValue.rawValue()); str += '%'; m_properties[type].insert(propName, str); } } -void KoGenStyle::addAttributePt(const QString& attrName, qreal attrValue) +void KoGenStyle::addAttribute(const QString& attrName, qreal attrValue) { QString str; str.setNum(attrValue, 'f', DBL_DIG); str += "pt"; m_attributes.insert(attrName, str); } void KoGenStyle::addAttributePercent(const QString &attrName, qreal value) { QByteArray str; str.setNum(value, 'f', FLT_DIG); str += '%'; addAttribute(attrName, str.data()); } void KoGenStyle::addAttributePercent(const QString &attrName, int value) { QByteArray str; str.setNum(value); str += '%'; addAttribute(attrName, str.data()); } void KoGenStyle::addStyleMap(const QMap& styleMap) { // check, if already present for (int i = 0 ; i < m_maps.count() ; ++i) { if (m_maps[i].count() == styleMap.count()) { int comp = compareMap(m_maps[i], styleMap); if (comp == 0) return; } } m_maps.append(styleMap); } #ifndef NDEBUG void KoGenStyle::printDebug() const { int i = DefaultType; debugOdf << m_properties[i].count() << " properties."; for (QMap::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } i = TextType; debugOdf << m_properties[i].count() << " text properties."; for (QMap::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } i = ParagraphType; debugOdf << m_properties[i].count() << " paragraph properties."; for (QMap::ConstIterator it = m_properties[i].constBegin(); it != m_properties[i].constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } i = TextType; debugOdf << m_childProperties[i].count() << " text child elements."; for (QMap::ConstIterator it = m_childProperties[i].constBegin(); it != m_childProperties[i].constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } i = ParagraphType; debugOdf << m_childProperties[i].count() << " paragraph child elements."; for (QMap::ConstIterator it = m_childProperties[i].constBegin(); it != m_childProperties[i].constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } debugOdf << m_attributes.count() << " attributes."; for (QMap::ConstIterator it = m_attributes.constBegin(); it != m_attributes.constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } debugOdf << m_maps.count() << " maps."; for (int i = 0; i < m_maps.count(); ++i) { debugOdf << "map" << i << ":"; for (QMap::ConstIterator it = m_maps[i].constBegin(); it != m_maps[i].constEnd(); ++it) { debugOdf << "" << it.key() << " =" << it.value(); } } debugOdf; } #endif bool KoGenStyle::operator<(const KoGenStyle &other) const { if (m_type != other.m_type) return m_type < other.m_type; if (m_parentName != other.m_parentName) return m_parentName < other.m_parentName; if (m_familyName != other.m_familyName) return m_familyName < other.m_familyName; if (m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml) return m_autoStyleInStylesDotXml; for (uint i = 0 ; i <= LastPropertyType; ++i) { if (m_properties[i].count() != other.m_properties[i].count()) { return m_properties[i].count() < other.m_properties[i].count(); } if (m_childProperties[i].count() != other.m_childProperties[i].count()) { return m_childProperties[i].count() < other.m_childProperties[i].count(); } } if (m_attributes.count() != other.m_attributes.count()) return m_attributes.count() < other.m_attributes.count(); if (m_maps.count() != other.m_maps.count()) return m_maps.count() < other.m_maps.count(); // Same number of properties and attributes, no other choice than iterating for (uint i = 0 ; i <= LastPropertyType; ++i) { int comp = compareMap(m_properties[i], other.m_properties[i]); if (comp != 0) return comp < 0; } for (uint i = 0 ; i <= LastPropertyType; ++i) { int comp = compareMap(m_childProperties[i], other.m_childProperties[i]); if (comp != 0) return comp < 0; } int comp = compareMap(m_attributes, other.m_attributes); if (comp != 0) return comp < 0; for (int i = 0 ; i < m_maps.count() ; ++i) { int comp = compareMap(m_maps[i], other.m_maps[i]); if (comp != 0) return comp < 0; } return false; } bool KoGenStyle::operator==(const KoGenStyle &other) const { if (m_type != other.m_type) return false; if (m_parentName != other.m_parentName) return false; if (m_familyName != other.m_familyName) return false; if (m_autoStyleInStylesDotXml != other.m_autoStyleInStylesDotXml) return false; for (uint i = 0 ; i <= LastPropertyType; ++i) { if (m_properties[i].count() != other.m_properties[i].count()) { return false; } if (m_childProperties[i].count() != other.m_childProperties[i].count()) { return false; } } if (m_attributes.count() != other.m_attributes.count()) return false; if (m_maps.count() != other.m_maps.count()) return false; // Same number of properties and attributes, no other choice than iterating for (uint i = 0 ; i <= LastPropertyType; ++i) { int comp = compareMap(m_properties[i], other.m_properties[i]); if (comp != 0) return false; } for (uint i = 0 ; i <= LastPropertyType; ++i) { int comp = compareMap(m_childProperties[i], other.m_childProperties[i]); if (comp != 0) return false; } int comp = compareMap(m_attributes, other.m_attributes); if (comp != 0) return false; for (int i = 0 ; i < m_maps.count() ; ++i) { int comp = compareMap(m_maps[i], other.m_maps[i]); if (comp != 0) return false; } return true; } bool KoGenStyle::isEmpty() const { if (!m_attributes.isEmpty() || ! m_maps.isEmpty()) return false; for (uint i = 0 ; i <= LastPropertyType; ++i) if (! m_properties[i].isEmpty()) return false; return true; } void KoGenStyle::copyPropertiesFromStyle(const KoGenStyle &sourceStyle, KoGenStyle &targetStyle, PropertyType type) { if (type == DefaultType) { type = sourceStyle.m_propertyType; } const StyleMap& map = sourceStyle.m_properties[type]; if (!map.isEmpty()) { QMap::const_iterator it = map.constBegin(); const QMap::const_iterator end = map.constEnd(); for (; it != end; ++it) { targetStyle.addProperty(it.key(), it.value(), type); } } } diff --git a/libs/odf/KoGenStyle.h b/libs/odf/KoGenStyle.h index 00dc489114..913df3ec41 100644 --- a/libs/odf/KoGenStyle.h +++ b/libs/odf/KoGenStyle.h @@ -1,549 +1,549 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007-2008 Thorsten Zachmann Copyright (C) 2009 Inge Wallin Copyright (C) 2010 KO GmbH Copyright (C) 2010 Jarosław Staniek Copyright (C) 2011 Pierre Ducroquet 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 KOGENSTYLE_H #define KOGENSTYLE_H #include #include #include #include "kritaodf_export.h" class QTextLength; class KoGenStyles; class KoXmlWriter; /** * A generic style, i.e. basically a collection of properties and a name. * Instances of KoGenStyle can either be held in the KoGenStyles collection, * or created (e.g. on the stack) and given to KoGenStyles::insert(). * * @author David Faure */ class KRITAODF_EXPORT KoGenStyle { public: /** * Possible values for the "type" argument of the KoGenStyle constructor. * @note If there is still something missing, add it here so that it is possible to use the same * saving code in all applications. */ enum Type { PageLayoutStyle, ///< style:page-layout as in odf 14.3 Page Layout TextStyle, ///< style:style from family "text" as in odf 14.8.1 Text Styles ///< (office:styles) TextAutoStyle, ///< style:style from family "text" as in odf 14.8.1 Text Styles ///< (office:automatic-styles) ParagraphStyle, ///< style:style from family "paragraph" as in odf 14.1 Style Element ///< (office:styles) ParagraphAutoStyle, ///< style:style from family "paragraph" as in odf 14.1 Style Element ///< (office:automatic-styles) SectionStyle, ///< style:style from family "section" as in odf 14.8.3 Section Styles ///< (office:styles) SectionAutoStyle, ///< style:style from family "section" as in odf 14.8.3 Section Styles ///< (office:automatic-styles) RubyStyle, ///< style:style from family "ruby" as in odf 14.8.4 Ruby Style ///< (office:styles) RubyAutoStyle, ///< style:style from family "ruby" as in odf 14.8.4 Ruby Style ///< (office:automatic-styles) TableStyle, ///< style:style from family "table" as in odf 14.12 Table Formatting ///< Properties (office:styles) TableAutoStyle, ///< style:style from family "table" as in odf 14.12 Table Formatting Properties ///< (office:automatic-styles) TableColumnStyle, ///< style:style from family "table-column" as in odf 15.9 Column Formatting ///< Properties (office:styles) TableColumnAutoStyle, ///< style:style from family "table-column" as in odf 15.9 Column Formatting ///< Properties (office:automatic-styles) TableRowStyle, ///< style:style from family "table-row" as in odf 15.10 Table Row Formatting ///< Properties (office:styles) TableRowAutoStyle, ///< style:style from family "table-row" as in odf 15.10 Table Row Formatting ///< Properties (office:automatic-styles) TableCellStyle, ///< style:style from family "table-cell" as in odf 15.11 Table Cell Formatting ///< Properties (office:styles) TableCellAutoStyle, ///< style:style from family "table-cell" as in odf 15.11 Table Cell Formatting ///< Properties (office:automatic-styles) GraphicStyle, ///< style:style from family "graphic" as in 14.13.1 Graphic and Presentation ///< Styles (office:automatic-styles) GraphicAutoStyle, ///< style:style from family "graphic" as in 14.13.1 Graphic and Presentation ///< Styles (office:automatic-styles) PresentationStyle, ///< style:style from family "presentation" as in 14.13.1 Graphic and ///< Presentation Styles (office:styles) PresentationAutoStyle, ///< style:style from family "presentation" as in 14.13.1 Graphic and ///< Presentation Styles (office:automatic-styles) DrawingPageStyle, ///< style:style from family "drawing-page" as in odf 14.13.2 Drawing Page Style ///< (office:styles) DrawingPageAutoStyle, ///< style:style from family "drawing-page" as in odf 14.13.2 Drawing Page Style ///< (office:automatic-styles) ChartStyle, ///< style:style from family "chart" as in odf 14.16 Chart Styles ///< (office:styles) ChartAutoStyle, ///< style:style from family "chart" as in odf 14.16 Chart Styles ///< (office:automatic-styles) ListStyle, ///< text:list-style as in odf 14.10 List Style (office:styles) ListAutoStyle, ///< text:list-style as in odf 14.10 List Style (office:automatic-styles) NumericNumberStyle, ///< number:number-style as in odf 14.7.1 Number Style NumericDateStyle, ///< number:date-style as in odf 14.7.4 Date Style NumericTimeStyle, ///< number:time-style as in odf 14.7.5 Time Style NumericFractionStyle, ///< number:number-style as in odf 14.7.1 Number Style NumericPercentageStyle, ///< number:percentage-style as in odf 14.7.3 Percentage Style NumericScientificStyle, ///< number:number-style as in odf 14.7.1 Number Style NumericCurrencyStyle, ///< number:currency-style as in odf 14.7.2 Currency Style NumericTextStyle, ///< number:text-style 14.7.7 Text Style ///< @note unused HatchStyle, ///< draw:hatch as in odf 14.14.3 Hatch (office:styles) StrokeDashStyle, ///< draw:stroke-dash as in odf 14.14.7 Stroke Dash (office:styles) GradientStyle, ///< draw:gradient as in odf 14.14.1 Gradient (office:styles) LinearGradientStyle, ///< svg:linearGradient as in odf 14.14.2 SVG Gradients (office:styles) RadialGradientStyle, ///< svg:radialGradient as in odf 14.14.2 SVG Gradients (office:styles) ConicalGradientStyle, ///< calligra:conicalGradient calligra extension for conical gradients FillImageStyle, ///< draw:fill-image as in odf 14.14.4 Fill Image (office:styles) NumericBooleanStyle, ///< number:boolean 14.7.6 Boolean Style ///< @note unused OpacityStyle, ///< draw:opacity as in odf 14.14.5 Opacity Gradient ///< @note unused MarkerStyle, ///< draw:marker as in odf 14.14.6 Marker PresentationPageLayoutStyle, ///< style:presentation-page-layout as in odf 14.15 Presentation Page Layouts OutlineLevelStyle, ///< text:outline-style as in odf 1.2 section 16.34 // TODO differently MasterPageStyle, ///< style:master-page as in odf 14.4 14.4 Master Pages (office:master-styles) // style:default-style as in odf 14.2 Default Styles // 14.5 Table Templates /// @internal @note always update when adding values to this enum LastStyle = MasterPageStyle }; /** * Start the definition of a new style. Its name will be set later by KoGenStyles::insert(), * but first you must define its properties and attributes. * * @param type this is a hook for the application to categorize styles * See the Style* enum. Ignored when writing out the style. * * @param familyName The value for style:family, e.g. text, paragraph, graphic etc. * The family is for style:style elements only; number styles and list styles don't have one. * * @param parentName If set, name of the parent style from which this one inherits. */ explicit KoGenStyle(Type type = PageLayoutStyle, const char *familyName = 0, const QString &parentName = QString()); ~KoGenStyle(); /** * setAutoStyleInStylesDotXml(true) marks a given automatic style as being needed in styles.xml. * For instance styles used by headers and footers need to go there, since * they are saved in styles.xml, and styles.xml must be independent from content.xml. * * The application should use KoGenStyles::styles( type, true ) in order to retrieve * those styles and save them separately. */ void setAutoStyleInStylesDotXml(bool b) { m_autoStyleInStylesDotXml = b; } /// @return the value passed to setAutoStyleInStylesDotXml; false by default bool autoStyleInStylesDotXml() const { return m_autoStyleInStylesDotXml; } /** * setDefaultStyle(true) marks a given style as being the default style. * This means we expect that you will call writeStyle( ...,"style:default-style"), * and its name will be omitted in the output. */ void setDefaultStyle(bool b) { m_defaultStyle = b; } /// @return the value passed to setDefaultStyle; false by default bool isDefaultStyle() const { return m_defaultStyle; } /// Return the type of this style, as set in the constructor Type type() const { return m_type; } /// Return the family name const char* familyName() const { return m_familyName.data(); } /// Sets the name of style's parent. void setParentName(const QString &name) { m_parentName = name; } /// Return the name of style's parent, if set QString parentName() const { return m_parentName; } /** * @brief The types of properties * * Simple styles only write one foo-properties tag, in which case they can just use DefaultType. * However a given style might want to write several kinds of properties, in which case it would * need to use other property types than the default one. * * For instance this style: * @code * * * * * * @endcode * would use DefaultType for chart-properties (and would pass "style:chart-properties" to writeStyle(), * and would use GraphicType and TextType. */ enum PropertyType { /** * DefaultType depends on family: e.g. paragraph-properties if family=paragraph * or on the type of style (e.g. page-layout -> page-layout-properties). * (In fact that tag name is the one passed to writeStyle) */ DefaultType, /// TextType is always text-properties. TextType, /// ParagraphType is always paragraph-properties. ParagraphType, /// GraphicType is always graphic-properties. GraphicType, /// SectionType is always section-properties. SectionType, /// RubyType is always ruby-properties. RubyType, /// TableType is always table-properties. TableType, /// TableColumnType is always table-column-properties TableColumnType, /// TableRowType is always table-row-properties. TableRowType, /// TableCellType is always for table-cell-properties. TableCellType, /// PresentationType is always for presentation-properties. PresentationType, /// DrawingPageType is always for drawing-page-properties. DrawingPageType, /// ChartType is always for chart-properties. ChartType, Reserved1, ///< @internal for binary compatible extensions /// For elements that are children of the style itself, not any of the properties StyleChildElement, /// @internal @note always update when adding values to this enum LastPropertyType = StyleChildElement }; /// Add a property to the style. Passing DefaultType as property type uses a style-type specific property type. void addProperty(const QString &propName, const QString &propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, propValue); } /// Overloaded version of addProperty that takes a char*, usually for "..." void addProperty(const QString &propName, const char *propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, QString::fromUtf8(propValue)); } /// Overloaded version of addProperty that converts an int to a string void addProperty(const QString &propName, int propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, QString::number(propValue)); } /// Overloaded version of addProperty that converts a bool to a string (false/true) void addProperty(const QString &propName, bool propValue, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].insert(propName, propValue ? "true" : "false"); } /** * Add a property which represents a distance, measured in pt * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits), * and the unit name ("pt") is appended to it. */ void addPropertyPt(const QString &propName, qreal propValue, PropertyType type = DefaultType); /** * Add a property which represents a length, measured in pt, or in percent * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits) or as integer (for percents), * and the unit name ("pt" or "%") is appended to it. */ void addPropertyLength(const QString &propName, const QTextLength &propValue, PropertyType type = DefaultType); /** * Remove a property from the style. Passing DefaultType as property type * uses a style-type specific property type. */ void removeProperty(const QString &propName, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].remove(propName); } /** * Remove properties of defined type from the style. Passing DefaultType * as property type uses a style-type specific property type. */ void removeAllProperties(PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_properties[type].clear(); } /** * Add an attribute to the style * The difference between property and attributes is a bit oasis-format-specific: * attributes are for the style element itself, and properties are in the style:properties child element */ void addAttribute(const QString &attrName, const QString& attrValue) { m_attributes.insert(attrName, attrValue); } /// Overloaded version of addAttribute that takes a char*, usually for "..." void addAttribute(const QString &attrName, const char* attrValue) { m_attributes.insert(attrName, QString::fromUtf8(attrValue)); } /// Overloaded version of addAttribute that converts an int to a string void addAttribute(const QString &attrName, int attrValue) { m_attributes.insert(attrName, QString::number(attrValue)); } /// Overloaded version of addAttribute that converts a bool to a string void addAttribute(const QString &attrName, bool attrValue) { m_attributes.insert(attrName, attrValue ? "true" : "false"); } /** * Add an attribute which represents a distance, measured in pt * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits), * and the unit name ("pt") is appended to it. */ - void addAttributePt(const QString &attrName, qreal attrValue); + void addAttribute(const QString &attrName, qreal attrValue); /** * Add an attribute that represents a percentage value as defined in ODF */ void addAttributePercent(const QString &attrName, qreal value); /** * Add an attribute that represents a percentage value as defined in ODF */ void addAttributePercent(const QString &attrName, int value); /** * Remove an attribute from the style. */ void removeAttribute(const QString &attrName) { m_attributes.remove(attrName); } /** * @brief Add a child element to the style properties. * * What is meant here is that the contents of the QString * will be written out literally. This means you should use * KoXmlWriter to generate it: * @code * QBuffer buffer; * buffer.open( QIODevice::WriteOnly ); * KoXmlWriter elementWriter( &buffer ); // TODO pass indentation level * elementWriter.startElement( "..." ); * ... * elementWriter.endElement(); * QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); * gs.addChildElement( "...", elementContents ); * @endcode * * The value of @p elementName is only used to set the order on how the child elements are written out. */ void addChildElement(const QString &elementName, const QString& elementContents, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_childProperties[type].insert(elementName, elementContents); } /** * Same like \a addChildElement above but with QByteArray to explicit convert from QByteArray * to QString using utf8 to prevent a dirty pitfall. */ void addChildElement(const QString &elementName, const QByteArray& elementContents, PropertyType type = DefaultType) { if (type == DefaultType) { type = m_propertyType; } m_childProperties[type].insert(elementName, QString::fromUtf8(elementContents)); } /** * Same like \a addChildElement above but adds a child style which is not child of any of the properties * The value of @p elementName is only used to set the order on how the child elements are written out. */ void addStyleChildElement(const QString &elementName, const QString& elementContents) { m_properties[StyleChildElement].insertMulti(elementName, elementContents); } /** * Same like \a addStyleChildElement above but with QByteArray to explicit convert from QByteArray * to QString using utf8 to prevent a dirty pitfall. * The value of @p elementName is only used to set the order on how the child elements are written out. */ void addStyleChildElement(const QString &elementName, const QByteArray& elementContents) { m_properties[StyleChildElement].insertMulti(elementName, QString::fromUtf8(elementContents)); } /** * @brief Add a style:map to the style. * @param styleMap the attributes for the map, associated as (name,value). */ void addStyleMap(const QMap &styleMap); /** * @return true if the style has no attributes, no properties, no style map etc. * This can be used by applications which do not save all attributes unconditionally, * but only those that differ from the parent. But note that KoGenStyles::insert() can't find this out... */ bool isEmpty() const; /** * Write the definition of this style to @p writer, using the OASIS format. * @param writer the KoXmlWriter in which @p elementName will be created and filled in * @param styles the styles collection, used to look up the parent style * @param elementName the name of the XML element, e.g. "style:style". Don't forget to * pass style:default-style if isDefaultStyle(). * @param name must come from the collection. It will be ignored if isDefaultStyle() is true. * @param propertiesElementName the name of the XML element with the style properties, * e.g. "style:text-properties". Can be 0 in special cases where there should be no such item, * in which case the attributes and elements are added under the style itself. * @param closeElement set it to false to be able to add more child elements to the style element * @param drawElement set it to true to add "draw:name" (used for gradient/hatch style) otherwise add "style:name" */ void writeStyle(KoXmlWriter *writer, const KoGenStyles &styles, const char *elementName, const QString &name, const char *propertiesElementName, bool closeElement = true, bool drawElement = false) const; /** * Write the definition of these style properties to @p writer, using the OASIS format. * @param writer the KoXmlWriter in which @p elementName will be created and filled in * @param type the type of properties to write * @param parentStyle the parent to this style */ void writeStyleProperties(KoXmlWriter *writer, PropertyType type, const KoGenStyle *parentStyle = 0) const; /** * QMap requires a complete sorting order. * Another solution would have been a qdict and a key() here, a la KoTextFormat, * but the key was difficult to generate. * Solutions with only a hash value (not representative of the whole data) * require us to write a hashtable by hand.... */ bool operator<(const KoGenStyle &other) const; /// Not needed for QMap, but can still be useful bool operator==(const KoGenStyle &other) const; /** * Returns a property of this style. In prinicpal this class is meant to be write-only, but * some exceptional cases having read-support as well is very useful. Passing DefaultType * as property type uses a style-type specific property type. */ QString property(const QString &propName, PropertyType type = DefaultType) const { if (type == DefaultType) { type = m_propertyType; } const QMap::const_iterator it = m_properties[type].constFind(propName); if (it != m_properties[type].constEnd()) return it.value(); return QString(); } /** * Returns a property of this style. In prinicpal this class is meant to be write-only, but * some exceptional cases having read-support as well is very useful. Passing DefaultType * as property type uses a style-type specific property type. */ QString childProperty(const QString &propName, PropertyType type = DefaultType) const { if (type == DefaultType) { type = m_propertyType; } const QMap::const_iterator it = m_childProperties[type].constFind(propName); if (it != m_childProperties[type].constEnd()) return it.value(); return QString(); } /// Returns an attribute of this style. In prinicpal this class is meant to be write-only, but some exceptional cases having read-support as well is very useful. QString attribute(const QString &propName) const { const QMap::const_iterator it = m_attributes.constFind(propName); if (it != m_attributes.constEnd()) return it.value(); return QString(); } /** * Copies properties of defined type from a style to another style. * This is needed in rare cases where two styles have properties of different types * and we want to merge them to one style. */ static void copyPropertiesFromStyle(const KoGenStyle &sourceStyle, KoGenStyle &targetStyle, PropertyType type = DefaultType); private: #ifndef NDEBUG void printDebug() const; #endif private: // Note that the copy constructor and assignment operator are allowed. // Better not use pointers below! // TODO turn this into a QSharedData class PropertyType m_propertyType; Type m_type; QByteArray m_familyName; QString m_parentName; /// We use QMaps since they provide automatic sorting on the key (important for unicity!) typedef QMap StyleMap; StyleMap m_properties[LastPropertyType+1]; StyleMap m_childProperties[LastPropertyType+1]; StyleMap m_attributes; QList m_maps; // we can't really sort the maps between themselves... bool m_autoStyleInStylesDotXml; bool m_defaultStyle; short m_unused2; // For insert() friend class KoGenStyles; }; #endif /* KOGENSTYLE_H */ diff --git a/libs/odf/KoOdfGraphicStyles.cpp b/libs/odf/KoOdfGraphicStyles.cpp index 09e4d0d66f..0555af6b43 100644 --- a/libs/odf/KoOdfGraphicStyles.cpp +++ b/libs/odf/KoOdfGraphicStyles.cpp @@ -1,783 +1,783 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007 Jan Hambrecht Copyright (C) 2007-2008,2010-2011 Thorsten Zachmann Copyright (C) 2011 Lukáš Tvrdý This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoOdfGraphicStyles.h" #include #include #include #include #include #include #include #include #include #include #include "KoOdfStylesReader.h" void KoOdfGraphicStyles::saveOdfFillStyle(KoGenStyle &styleFill, KoGenStyles& mainStyles, const QBrush & brush) { KoGenStyle::Type type = styleFill.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; switch (brush.style()) { case Qt::Dense1Pattern: styleFill.addProperty("draw:opacity", "6%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::Dense2Pattern: styleFill.addProperty("draw:opacity", "12%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::Dense3Pattern: styleFill.addProperty("draw:opacity", "37%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::Dense4Pattern: styleFill.addProperty("draw:opacity", "50%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::Dense5Pattern: styleFill.addProperty("draw:opacity", "63%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::Dense6Pattern: styleFill.addProperty("draw:opacity", "88%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::Dense7Pattern: styleFill.addProperty("draw:opacity", "94%", propertyType); styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); break; case Qt::LinearGradientPattern: case Qt::RadialGradientPattern: case Qt::ConicalGradientPattern: styleFill.addProperty("draw:fill", "gradient", propertyType); styleFill.addProperty("draw:fill-gradient-name", saveOdfGradientStyle(mainStyles, brush), propertyType); break; case Qt::HorPattern: case Qt::VerPattern: case Qt::CrossPattern: case Qt::BDiagPattern: case Qt::FDiagPattern: case Qt::DiagCrossPattern: styleFill.addProperty("draw:fill", "hatch", propertyType); styleFill.addProperty("draw:fill-hatch-name", saveOdfHatchStyle(mainStyles, brush), propertyType); break; case Qt::SolidPattern: styleFill.addProperty("draw:fill", "solid", propertyType); styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType); if (! brush.isOpaque()) styleFill.addProperty("draw:opacity", QString("%1%").arg(brush.color().alphaF() * 100.0), propertyType); break; case Qt::NoBrush: default: styleFill.addProperty("draw:fill", "none", propertyType); break; } } void KoOdfGraphicStyles::saveOdfStrokeStyle(KoGenStyle &styleStroke, KoGenStyles &mainStyles, const QPen &pen) { // TODO implement all possibilities switch (pen.style()) { case Qt::NoPen: styleStroke.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); return; case Qt::SolidLine: styleStroke.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType); break; default: { // must be a dashed line styleStroke.addProperty("draw:stroke", "dash", KoGenStyle::GraphicType); // save stroke dash (14.14.7) which is severely limited, but still KoGenStyle dashStyle(KoGenStyle::StrokeDashStyle); dashStyle.addAttribute("draw:style", "rect"); QVector dashes = pen.dashPattern(); dashStyle.addAttribute("draw:dots1", static_cast(1)); - dashStyle.addAttributePt("draw:dots1-length", dashes[0]*pen.widthF()); - dashStyle.addAttributePt("draw:distance", dashes[1]*pen.widthF()); + dashStyle.addAttribute("draw:dots1-length", dashes[0]*pen.widthF()); + dashStyle.addAttribute("draw:distance", dashes[1]*pen.widthF()); if (dashes.size() > 2) { dashStyle.addAttribute("draw:dots2", static_cast(1)); - dashStyle.addAttributePt("draw:dots2-length", dashes[2]*pen.widthF()); + dashStyle.addAttribute("draw:dots2-length", dashes[2]*pen.widthF()); } QString dashStyleName = mainStyles.insert(dashStyle, "dash"); styleStroke.addProperty("draw:stroke-dash", dashStyleName, KoGenStyle::GraphicType); break; } } if (pen.brush().gradient()) { styleStroke.addProperty("calligra:stroke-gradient", saveOdfGradientStyle(mainStyles, pen.brush()), KoGenStyle::GraphicType); } else { styleStroke.addProperty("svg:stroke-color", pen.color().name(), KoGenStyle::GraphicType); styleStroke.addProperty("svg:stroke-opacity", QString("%1").arg(pen.color().alphaF()), KoGenStyle::GraphicType); } styleStroke.addPropertyPt("svg:stroke-width", pen.widthF(), KoGenStyle::GraphicType); switch (pen.joinStyle()) { case Qt::MiterJoin: styleStroke.addProperty("draw:stroke-linejoin", "miter", KoGenStyle::GraphicType); break; case Qt::BevelJoin: styleStroke.addProperty("draw:stroke-linejoin", "bevel", KoGenStyle::GraphicType); break; case Qt::RoundJoin: styleStroke.addProperty("draw:stroke-linejoin", "round", KoGenStyle::GraphicType); break; default: styleStroke.addProperty("draw:stroke-linejoin", "miter", KoGenStyle::GraphicType); styleStroke.addProperty("calligra:stroke-miterlimit", QString("%1").arg(pen.miterLimit()), KoGenStyle::GraphicType); break; } switch (pen.capStyle()) { case Qt::RoundCap: styleStroke.addProperty("svg:stroke-linecap", "round", KoGenStyle::GraphicType); break; case Qt::SquareCap: styleStroke.addProperty("svg:stroke-linecap", "square", KoGenStyle::GraphicType); break; default: styleStroke.addProperty("svg:stroke-linecap", "butt", KoGenStyle::GraphicType); break; } } QString KoOdfGraphicStyles::saveOdfHatchStyle(KoGenStyles& mainStyles, const QBrush &brush) { KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/); hatchStyle.addAttribute("draw:color", brush.color().name()); //hatchStyle.addAttribute( "draw:distance", m_distance ); not implemented into kpresenter switch (brush.style()) { case Qt::HorPattern: hatchStyle.addAttribute("draw:style", "single"); hatchStyle.addAttribute("draw:rotation", 0); break; case Qt::BDiagPattern: hatchStyle.addAttribute("draw:style", "single"); hatchStyle.addAttribute("draw:rotation", 450); break; case Qt::VerPattern: hatchStyle.addAttribute("draw:style", "single"); hatchStyle.addAttribute("draw:rotation", 900); break; case Qt::FDiagPattern: hatchStyle.addAttribute("draw:style", "single"); hatchStyle.addAttribute("draw:rotation", 1350); break; case Qt::CrossPattern: hatchStyle.addAttribute("draw:style", "double"); hatchStyle.addAttribute("draw:rotation", 0); break; case Qt::DiagCrossPattern: hatchStyle.addAttribute("draw:style", "double"); hatchStyle.addAttribute("draw:rotation", 450); break; default: break; } return mainStyles.insert(hatchStyle, "hatch"); } QString KoOdfGraphicStyles::saveOdfGradientStyle(KoGenStyles &mainStyles, const QBrush &brush) { KoGenStyle gradientStyle; if (brush.style() == Qt::RadialGradientPattern) { const QRadialGradient *gradient = static_cast(brush.gradient()); gradientStyle = KoGenStyle(KoGenStyle::RadialGradientStyle /*no family name*/); gradientStyle.addAttributePercent("svg:cx", gradient->center().x() * 100); gradientStyle.addAttributePercent("svg:cy", gradient->center().y() * 100); gradientStyle.addAttributePercent("svg:r", gradient->radius() * 100); gradientStyle.addAttributePercent("svg:fx", gradient->focalPoint().x() * 100); gradientStyle.addAttributePercent("svg:fy", gradient->focalPoint().y() * 100); } else if (brush.style() == Qt::LinearGradientPattern) { const QLinearGradient *gradient = static_cast(brush.gradient()); gradientStyle = KoGenStyle(KoGenStyle::LinearGradientStyle /*no family name*/); gradientStyle.addAttributePercent("svg:x1", gradient->start().x() * 100); gradientStyle.addAttributePercent("svg:y1", gradient->start().y() * 100); gradientStyle.addAttributePercent("svg:x2", gradient->finalStop().x() * 100); gradientStyle.addAttributePercent("svg:y2", gradient->finalStop().y() * 100); } else if (brush.style() == Qt::ConicalGradientPattern) { const QConicalGradient * gradient = static_cast(brush.gradient()); gradientStyle = KoGenStyle(KoGenStyle::ConicalGradientStyle /*no family name*/); gradientStyle.addAttributePercent("svg:cx", gradient->center().x() * 100); gradientStyle.addAttributePercent("svg:cy", gradient->center().y() * 100); gradientStyle.addAttribute("draw:angle", QString("%1").arg(gradient->angle())); } const QGradient * gradient = brush.gradient(); if (gradient->spread() == QGradient::RepeatSpread) gradientStyle.addAttribute("svg:spreadMethod", "repeat"); else if (gradient->spread() == QGradient::ReflectSpread) gradientStyle.addAttribute("svg:spreadMethod", "reflect"); else gradientStyle.addAttribute("svg:spreadMethod", "pad"); if (! brush.transform().isIdentity()) { gradientStyle.addAttribute("svg:gradientTransform", saveTransformation(brush.transform())); } QBuffer buffer; buffer.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buffer); // TODO pass indentation level // save stops QGradientStops stops = gradient->stops(); Q_FOREACH (const QGradientStop & stop, stops) { elementWriter.startElement("svg:stop"); elementWriter.addAttribute("svg:offset", QString("%1").arg(stop.first)); elementWriter.addAttribute("svg:stop-color", stop.second.name()); if (stop.second.alphaF() < 1.0) elementWriter.addAttribute("svg:stop-opacity", QString("%1").arg(stop.second.alphaF())); elementWriter.endElement(); } QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size()); gradientStyle.addChildElement("svg:stop", elementContents); return mainStyles.insert(gradientStyle, "gradient"); } QBrush KoOdfGraphicStyles::loadOdfGradientStyle(const KoStyleStack &styleStack, const KoOdfStylesReader & stylesReader, const QSizeF &size) { QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name"); return loadOdfGradientStyleByName(stylesReader, styleName, size); } qreal percent(const KoXmlElement &element, const QString &ns, const QString &type, const QString &defaultValue, qreal absolute) { qreal tmp = 0.0; QString value = element.attributeNS(ns, type, defaultValue); if (value.indexOf('%') > -1) { // percent value tmp = value.remove('%').toDouble() / 100.0; } else { // fixed value tmp = KoUnit::parseValue(value) / absolute; // The following is done so that we get the same data as when we save/load. // This is needed that we get the same values due to rounding differences // of absolute and relative values. QString value = QString("%1").arg(tmp * 100.0); tmp = value.toDouble() / 100; } return tmp; } QBrush KoOdfGraphicStyles::loadOdfGradientStyleByName(const KoOdfStylesReader &stylesReader, const QString &styleName, const QSizeF &size) { KoXmlElement* e = stylesReader.drawStyles("gradient")[styleName]; if (! e) return QBrush(); QGradient * gradient = 0; QTransform transform; if (e->namespaceURI() == KoXmlNS::draw && e->localName() == "gradient") { // FIXME seems like oo renders the gradient start stop color at the center of the // radial gradient, and the start color at the radius of the radial gradient // whereas it is not mentioned in the spec how it should be rendered // note that svg defines that exactly as the opposite as oo does // so what should we do? QString type = e->attributeNS(KoXmlNS::draw, "style", QString()); if (type == "radial") { // Zagge: at the moment the only objectBoundingBox is supported: // 18.539 svg:gradientUnits // See §13.2.2 and §13.2.3 of [SVG]. // The default value for this attribute is objectBoundingBox. // The only value of the svg:gradientUnits attribute is objectBoundingBox. qreal cx = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "cx", QString()).remove('%')); qreal cy = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "cy", QString()).remove('%')); gradient = new QRadialGradient(QPointF(cx * 0.01, cy * 0.01), sqrt(0.5)); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); } else if (type == "linear" || type == "axial") { QLinearGradient * lg = new QLinearGradient(); lg->setCoordinateMode(QGradient::ObjectBoundingMode); // Dividing by 10 here because OOo saves as degree * 10 qreal angle = 90 + e->attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10; qreal radius = sqrt(0.5); qreal sx = cos(angle * M_PI / 180) * radius; qreal sy = sin(angle * M_PI / 180) * radius; lg->setStart(QPointF(0.5 + sx, 0.5 - sy)); lg->setFinalStop(QPointF(0.5 - sx, 0.5 + sy)); gradient = lg; } else return QBrush(); qreal border = 0.01 * e->attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble(); QGradientStops stops; if (type != "axial") { // In case of radial gradients the colors are reversed, because OOo saves them as the oppsite of the SVG direction // see bug 137639 QGradientStop start; start.first = (type != "radial") ? border : 1.0 - border; start.second = QColor(e->attributeNS(KoXmlNS::draw, "start-color", QString())); start.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble()); QGradientStop end; end.first = (type != "radial") ? 1.0 : 0.0; end.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString())); end.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); stops << start << end; } else { QGradientStop start; start.first = 0.5 * border; start.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString())); start.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); QGradientStop middle; middle.first = 0.5; middle.second = QColor(e->attributeNS(KoXmlNS::draw, "start-color", QString())); middle.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble()); QGradientStop end; end.first = 1.0 - 0.5 * border; end.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString())); end.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); stops << start << middle << end; } gradient->setStops(stops); } else if (e->namespaceURI() == KoXmlNS::svg) { if (e->localName() == "linearGradient") { QPointF start, stop; start.setX(percent(*e, KoXmlNS::svg, "x1", "0%", size.width())); start.setY(percent(*e, KoXmlNS::svg, "y1", "0%", size.height())); stop.setX(percent(*e, KoXmlNS::svg, "x2", "100%", size.width())); stop.setY(percent(*e, KoXmlNS::svg, "y2", "100%", size.height())); gradient = new QLinearGradient(start, stop); } else if (e->localName() == "radialGradient") { QPointF center, focalPoint; center.setX(percent(*e, KoXmlNS::svg, "cx", "50%", size.width())); center.setY(percent(*e, KoXmlNS::svg, "cy", "50%", size.height())); qreal r = percent(*e, KoXmlNS::svg, "r", "50%", sqrt(size.width() * size.width() + size.height() * size.height())); focalPoint.setX(percent(*e, KoXmlNS::svg, "fx", QString(), size.width())); focalPoint.setY(percent(*e, KoXmlNS::svg, "fy", QString(), size.height())); gradient = new QRadialGradient(center, r, focalPoint ); } if (! gradient) return QBrush(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); QString strSpread(e->attributeNS(KoXmlNS::svg, "spreadMethod", "pad")); if (strSpread == "repeat") gradient->setSpread(QGradient::RepeatSpread); else if (strSpread == "reflect") gradient->setSpread(QGradient::ReflectSpread); else gradient->setSpread(QGradient::PadSpread); if (e->hasAttributeNS(KoXmlNS::svg, "gradientTransform")) transform = loadTransformation(e->attributeNS(KoXmlNS::svg, "gradientTransform", QString())); QGradientStops stops; // load stops KoXmlElement colorstop; forEachElement(colorstop, (*e)) { if (colorstop.namespaceURI() == KoXmlNS::svg && colorstop.localName() == "stop") { QGradientStop stop; stop.second = QColor(colorstop.attributeNS(KoXmlNS::svg, "stop-color", QString())); stop.second.setAlphaF(colorstop.attributeNS(KoXmlNS::svg, "stop-opacity", "1.0").toDouble()); stop.first = colorstop.attributeNS(KoXmlNS::svg, "offset", "0.0").toDouble(); stops.append(stop); } } gradient->setStops(stops); } else if (e->namespaceURI() == KoXmlNS::calligra) { if (e->localName() == "conicalGradient") { QPointF center; center.setX(percent(*e, KoXmlNS::svg, "cx", "50%", size.width())); center.setY(percent(*e, KoXmlNS::svg, "cy", "50%", size.height())); qreal angle = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "angle", QString())); gradient = new QConicalGradient(center, angle); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); QString strSpread(e->attributeNS(KoXmlNS::svg, "spreadMethod", "pad")); if (strSpread == "repeat") gradient->setSpread(QGradient::RepeatSpread); else if (strSpread == "reflect") gradient->setSpread(QGradient::ReflectSpread); else gradient->setSpread(QGradient::PadSpread); if (e->hasAttributeNS(KoXmlNS::svg, "gradientTransform")) transform = loadTransformation(e->attributeNS(KoXmlNS::svg, "gradientTransform", QString())); QGradientStops stops; // load stops KoXmlElement colorstop; forEachElement(colorstop, (*e)) { if (colorstop.namespaceURI() == KoXmlNS::svg && colorstop.localName() == "stop") { QGradientStop stop; stop.second = QColor(colorstop.attributeNS(KoXmlNS::svg, "stop-color", QString())); stop.second.setAlphaF(colorstop.attributeNS(KoXmlNS::svg, "stop-opacity", "1.0").toDouble()); stop.first = colorstop.attributeNS(KoXmlNS::svg, "offset", "0.0").toDouble(); stops.append(stop); } } gradient->setStops(stops); } } if (! gradient) return QBrush(); QBrush resultBrush(*gradient); resultBrush.setTransform(transform); delete gradient; return resultBrush; } QBrush KoOdfGraphicStyles::loadOdfFillStyle(const KoStyleStack &styleStack, const QString & fill, const KoOdfStylesReader & stylesReader) { QBrush tmpBrush; // default brush for "none" is a Qt::NoBrush if (fill == "solid") { tmpBrush.setStyle(Qt::SolidPattern); if (styleStack.hasProperty(KoXmlNS::draw, "fill-color")) tmpBrush.setColor(styleStack.property(KoXmlNS::draw, "fill-color")); if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacity = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") { float percent = opacity.left(opacity.length() - 1).toFloat(); QColor color = tmpBrush.color(); color.setAlphaF(percent / 100.0); tmpBrush.setColor(color); } } //TODO if (styleStack.hasProperty(KoXmlNS::draw, "transparency")) { QString transparency = styleStack.property(KoXmlNS::draw, "transparency"); if (transparency == "94%") { tmpBrush.setStyle(Qt::Dense1Pattern); } else if (transparency == "88%") { tmpBrush.setStyle(Qt::Dense2Pattern); } else if (transparency == "63%") { tmpBrush.setStyle(Qt::Dense3Pattern); } else if (transparency == "50%") { tmpBrush.setStyle(Qt::Dense4Pattern); } else if (transparency == "37%") { tmpBrush.setStyle(Qt::Dense5Pattern); } else if (transparency == "12%") { tmpBrush.setStyle(Qt::Dense6Pattern); } else if (transparency == "6%") { tmpBrush.setStyle(Qt::Dense7Pattern); } else debugOdf << " transparency is not defined into kpresenter :" << transparency; } } else if (fill == "hatch") { QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name"); debugOdf << " hatch style is :" << style; //type not defined by default //try to use style. KoXmlElement* draw = stylesReader.drawStyles("hatch")[style]; if (draw) { debugOdf << "We have a style"; int angle = 0; if (draw->hasAttributeNS(KoXmlNS::draw, "rotation")) { angle = (draw->attributeNS(KoXmlNS::draw, "rotation", QString()).toInt()) / 10; debugOdf << "angle :" << angle; } if (draw->hasAttributeNS(KoXmlNS::draw, "color")) { //debugOdf<<" draw:color :"<attributeNS( KoXmlNS::draw,"color", QString() ); tmpBrush.setColor(draw->attributeNS(KoXmlNS::draw, "color", QString())); } if (draw->hasAttributeNS(KoXmlNS::draw, "distance")) { //todo implement it into kpresenter } if (draw->hasAttributeNS(KoXmlNS::draw, "display-name")) { //todo implement it into kpresenter } if (draw->hasAttributeNS(KoXmlNS::draw, "style")) { //todo implement it into kpresenter QString styleHash = draw->attributeNS(KoXmlNS::draw, "style", QString()); if (styleHash == "single") { switch (angle) { case 0: case 180: tmpBrush.setStyle(Qt::HorPattern); break; case 45: case 225: tmpBrush.setStyle(Qt::BDiagPattern); break; case 90: case 270: tmpBrush.setStyle(Qt::VerPattern); break; case 135: case 315: tmpBrush.setStyle(Qt::FDiagPattern); break; default: //todo fixme when we will have a kopaint debugOdf << " draw:rotation 'angle' :" << angle; break; } } else if (styleHash == "double") { switch (angle) { case 0: case 180: case 90: case 270: tmpBrush.setStyle(Qt::CrossPattern); break; case 45: case 135: case 225: case 315: tmpBrush.setStyle(Qt::DiagCrossPattern); break; default: //todo fixme when we will have a kopaint debugOdf << " draw:rotation 'angle' :" << angle; break; } } else if (styleHash == "triple") { debugOdf << " it is not implemented :("; } } } } return tmpBrush; } static qreal parseDashEntrySize(QString& attr, qreal penWidth, qreal defaultValue = 0.0){ qreal result = defaultValue; if (attr.endsWith('%')) { bool ok; const int percent = attr.remove('%').toInt(&ok); if (ok && percent >= 0) { result = percent / 100.0; } } else { result = KoUnit::parseValue(attr) / penWidth; } return result; } QPen KoOdfGraphicStyles::loadOdfStrokeStyle(const KoStyleStack &styleStack, const QString & stroke, const KoOdfStylesReader & stylesReader) { QPen tmpPen(Qt::NoPen); // default pen for "none" is a Qt::NoPen if (stroke == "solid" || stroke == "dash") { // If solid or dash is set then we assume that the color is black and the penWidth // is zero till defined otherwise with the following attributes. tmpPen = QPen(); if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) tmpPen.setColor(styleStack.property(KoXmlNS::svg, "stroke-color")); if (styleStack.hasProperty(KoXmlNS::svg, "stroke-opacity")) { QColor color = tmpPen.color(); QString opacity = styleStack.property(KoXmlNS::svg, "stroke-opacity"); if (opacity.endsWith('%')) color.setAlphaF(0.01 * opacity.remove('%').toDouble()); else color.setAlphaF(opacity.toDouble()); tmpPen.setColor(color); } if (styleStack.hasProperty(KoXmlNS::svg, "stroke-width")) tmpPen.setWidthF(KoUnit::parseValue(styleStack.property(KoXmlNS::svg, "stroke-width"))); if (styleStack.hasProperty(KoXmlNS::draw, "stroke-linejoin")) { QString join = styleStack.property(KoXmlNS::draw, "stroke-linejoin"); if (join == "bevel") tmpPen.setJoinStyle(Qt::BevelJoin); else if (join == "round") tmpPen.setJoinStyle(Qt::RoundJoin); else { tmpPen.setJoinStyle(Qt::MiterJoin); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-miterlimit")) { QString miterLimit = styleStack.property(KoXmlNS::calligra, "stroke-miterlimit"); tmpPen.setMiterLimit(miterLimit.toDouble()); } } } if (styleStack.hasProperty(KoXmlNS::svg, "stroke-linecap")) { const QString cap = styleStack.property(KoXmlNS::svg, "stroke-linecap"); if (cap == "round") tmpPen.setCapStyle(Qt::RoundCap); else if (cap == "square") tmpPen.setCapStyle(Qt::SquareCap); else tmpPen.setCapStyle(Qt::FlatCap); } else { // default as per svg specification tmpPen.setCapStyle(Qt::FlatCap); } if (stroke == "dash" && styleStack.hasProperty(KoXmlNS::draw, "stroke-dash")) { QString dashStyleName = styleStack.property(KoXmlNS::draw, "stroke-dash"); // set width to 1 in case it is 0 as dividing by 0 gives infinity qreal width = tmpPen.widthF(); if ( width == 0 ) { width = 1; } KoXmlElement * dashElement = stylesReader.drawStyles("stroke-dash")[ dashStyleName ]; if (dashElement) { QVector dashes; if (dashElement->hasAttributeNS(KoXmlNS::draw, "dots1")) { QString distance( dashElement->attributeNS(KoXmlNS::draw, "distance", QString()) ); qreal space = parseDashEntrySize(distance, width, 0.0); QString dots1Length(dashElement->attributeNS(KoXmlNS::draw, "dots1-length", QString())); qreal dot1Length = parseDashEntrySize(dots1Length,width,1.0); bool ok; int dots1 = dashElement->attributeNS(KoXmlNS::draw, "dots1").toInt(&ok); if (!ok) { dots1 = 1; } for (int i = 0; i < dots1; i++) { dashes.append(dot1Length); dashes.append(space); } if (dashElement->hasAttributeNS(KoXmlNS::draw, "dots2")) { QString dots2Length(dashElement->attributeNS(KoXmlNS::draw, "dots2-length", QString())); qreal dot2Length = parseDashEntrySize(dots2Length,width,1.0); int dots2 = dashElement->attributeNS(KoXmlNS::draw, "dots2").toInt(&ok); if (!ok) { dots2 = 1; } for (int i = 0; i < dots2; i++) { dashes.append(dot2Length); dashes.append(space); } } tmpPen.setDashPattern(dashes); } } } } return tmpPen; } QTransform KoOdfGraphicStyles::loadTransformation(const QString &transformation) { QTransform transform; // Split string for handling 1 transform statement at a time QStringList subtransforms = transformation.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); if (subtransform[0] == "rotate") { // TODO find out what oo2 really does when rotating, it seems severely broken if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); transform.translate(x, y); // oo2 rotates by radians transform.rotate(params[0].toDouble()*180.0 / M_PI); transform.translate(-x, -y); } else { // oo2 rotates by radians transform.rotate(params[0].toDouble()*180.0 / M_PI); } } else if (subtransform[0] == "translate") { if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); transform.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 transform.translate(KoUnit::parseValue(params[0]) , 0); } else if (subtransform[0] == "scale") { if (params.count() == 2) transform.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling transform.scale(params[0].toDouble(), params[0].toDouble()); } else if (subtransform[0] == "skewx") transform.shear(tan(params[0].toDouble()), 0.0F); else if (subtransform[0] == "skewy") transform.shear(tan(params[0].toDouble()), 0.0F); else if (subtransform[0] == "matrix") { if (params.count() >= 6) { transform.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } } } return transform; } QString KoOdfGraphicStyles::saveTransformation(const QTransform &transformation, bool appendTranslateUnit) { QString transform; if (appendTranslateUnit) transform = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(transformation.m11()).arg(transformation.m12()) .arg(transformation.m21()).arg(transformation.m22()) .arg(transformation.dx()) .arg(transformation.dy()); else transform = QString("matrix(%1 %2 %3 %4 %5 %6)") .arg(transformation.m11()).arg(transformation.m12()) .arg(transformation.m21()).arg(transformation.m22()) .arg(transformation.dx()) .arg(transformation.dy()); return transform; } diff --git a/libs/odf/tests/CMakeLists.txt b/libs/odf/tests/CMakeLists.txt index 9dbf046647..1db98720bb 100644 --- a/libs/odf/tests/CMakeLists.txt +++ b/libs/odf/tests/CMakeLists.txt @@ -1,22 +1,21 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) ecm_add_tests( TestKoGenStyles.cpp TestOdfSettings.cpp TestKoOdfLoadingContext.cpp TestStorage.cpp NAME_PREFIX "libs-odf-" LINK_LIBRARIES kritaodf KF5::I18n Qt5::Test) ecm_add_tests( TestXmlWriter.cpp - TestXmlReader.cpp kodomtest.cpp TestKoUnit.cpp TestNumberStyle.cpp TestKoElementReference.cpp NAME_PREFIX "libs-odf-" LINK_LIBRARIES kritaodf Qt5::Test) diff --git a/libs/odf/tests/TestXmlReader.cpp b/libs/odf/tests/TestXmlReader.cpp deleted file mode 100644 index ef6be4d67e..0000000000 --- a/libs/odf/tests/TestXmlReader.cpp +++ /dev/null @@ -1,2775 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include - - -class TestXmlReader : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void testNode(); - void testElement(); - void testAttributes(); - void testText(); - void testCDATA(); - void testDocument(); - void testDocumentType(); - void testNamespace(); - void testParseQString(); - void testUnload(); - void testSimpleXML(); - void testRootError(); - void testMismatchedTag(); - void testConvertQDomDocument(); - void testConvertQDomElement(); - void testSimpleOpenDocumentText(); - void testWhitespace(); - void testSimpleOpenDocumentSpreadsheet(); - void testSimpleOpenDocumentPresentation(); - void testSimpleOpenDocumentFormula(); - void testLargeOpenDocumentSpreadsheet(); - void testExternalOpenDocumentSpreadsheet(const QString& filename); -}; - -void TestXmlReader::testNode() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // null node - KoXmlNode node1; - QCOMPARE(node1.nodeName(), QString()); - QCOMPARE(node1.isNull(), true); - QCOMPARE(node1.isElement(), false); - QCOMPARE(node1.isDocument(), false); - QCOMPARE(node1.ownerDocument().isNull(), true); - QCOMPARE(node1.parentNode().isNull(), true); - QCOMPARE(node1.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(node1), 0); - QCOMPARE(node1.firstChild().isNull(), true); - QCOMPARE(node1.lastChild().isNull(), true); - QCOMPARE(node1.previousSibling().isNull(), true); - QCOMPARE(node1.nextSibling().isNull(), true); - - // compare with another null node - KoXmlNode node2; - QCOMPARE(node2.isNull(), true); - QCOMPARE(node1 == node2, true); - QCOMPARE(node1 != node2, false); - - // a node which is a document - KoXmlNode node3 = doc; - QCOMPARE(node3.nodeName(), QString("#document")); - QCOMPARE(node3.isNull(), false); - QCOMPARE(node3.isElement(), false); - QCOMPARE(node3.isText(), false); - QCOMPARE(node3.isDocument(), true); - QCOMPARE(node3.ownerDocument().isNull(), false); - QCOMPARE(node3.ownerDocument() == doc, true); - QCOMPARE(node3.toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(node3), 1); - - // convert to document and the compare - KoXmlDocument doc2 = node3.toDocument(); - QCOMPARE(doc2.nodeName(), QString("#document")); - QCOMPARE(doc2.isNull(), false); - QCOMPARE(doc2.isDocument(), true); - QCOMPARE(node3 == doc2, true); - QCOMPARE(KoXml::childNodesCount(doc2), 1); - - // a document is of course can't be converted to element - KoXmlElement invalidElement = node3.toElement(); - QCOMPARE(invalidElement.nodeName(), QString()); - QCOMPARE(invalidElement.isNull(), true); - QCOMPARE(invalidElement.isElement(), false); - QCOMPARE(invalidElement.isText(), false); - QCOMPARE(invalidElement.isDocument(), false); - - // clear() makes it a null node again - node3.clear(); - QCOMPARE(node3.isNull(), true); - QCOMPARE(node3.nodeName(), QString()); - QCOMPARE(node3.isElement(), false); - QCOMPARE(node3.isText(), false); - QCOMPARE(node3.isDocument(), false); - QCOMPARE(node3.ownerDocument().isNull(), true); - QCOMPARE(node1 == node3, true); - QCOMPARE(node1 != node3, false); - - // a node which is an element for - KoXmlNode node4 = doc.firstChild(); - QCOMPARE(node4.isNull(), false); - QCOMPARE(node4.isElement(), true); - QCOMPARE(node4.isText(), false); - QCOMPARE(node4.isDocument(), false); - QCOMPARE(node4.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(node4), 4); - QCOMPARE(node4.ownerDocument() == doc, true); - QCOMPARE(node4.toElement() == doc.firstChild().toElement(), true); - - // clear() makes it a null node again - node4.clear(); - QCOMPARE(node4.isNull(), true); - QCOMPARE(node4.isElement(), false); - QCOMPARE(node4.isText(), false); - QCOMPARE(node4.isDocument(), false); - QCOMPARE(node4 == node1, true); - QCOMPARE(node4 != node1, false); - QCOMPARE(KoXml::childNodesCount(node4), 0); - - // a node which is an element for - KoXmlNode node5 = doc.firstChild().firstChild().nextSibling(); - QCOMPARE(node5.nodeName(), QString("continents")); - QCOMPARE(node5.isNull(), false); - QCOMPARE(node5.isElement(), true); - QCOMPARE(node5.isText(), false); - QCOMPARE(node5.isDocument(), false); - QCOMPARE(node5.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(node5), 13); - QCOMPARE(node5.ownerDocument() == doc, true); - - // convert to element and the compare - KoXmlElement continentsElement = node5.toElement(); - QCOMPARE(node5 == continentsElement, true); - QCOMPARE(continentsElement.isNull(), false); - QCOMPARE(continentsElement.isElement(), true); - QCOMPARE(continentsElement.isText(), false); - QCOMPARE(continentsElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(continentsElement), 13); - QCOMPARE(continentsElement.ownerDocument() == doc, true); - - // and it doesn't make sense to convert that node to document - KoXmlDocument invalidDoc = node5.toDocument(); - QCOMPARE(invalidDoc.isNull(), true); - QCOMPARE(invalidDoc.isElement(), false); - QCOMPARE(invalidDoc.isText(), false); - QCOMPARE(invalidDoc.isDocument(), false); - - // node for using namedItem() function - KoXmlNode europeNode = continentsElement.namedItem(QString("europe")); - QCOMPARE(europeNode.nodeName(), QString("europe")); - QCOMPARE(europeNode.isNull(), false); - QCOMPARE(europeNode.isElement(), true); - QCOMPARE(europeNode.isText(), false); - QCOMPARE(europeNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(europeNode), 0); - QCOMPARE(europeNode.ownerDocument() == doc, true); - - // search non-existing node - KoXmlNode fooNode = continentsElement.namedItem(QString("foobar")); - QCOMPARE(fooNode.isNull(), true); - QCOMPARE(fooNode.isElement(), false); - QCOMPARE(fooNode.isText(), false); - QCOMPARE(fooNode.isCDATASection(), false); - QCOMPARE(KoXml::childNodesCount(fooNode), 0); -} - -void TestXmlReader::testElement() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << ""; - xmlstream << "

"; - xmlstream << "Hello, world!"; - xmlstream << "

"; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // element for - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.nodeName(), QString("html")); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.isDocument(), false); - QCOMPARE(rootElement.ownerDocument().isNull(), false); - QCOMPARE(rootElement.ownerDocument() == doc, true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 1); - QCOMPARE(rootElement.tagName(), QString("html")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // element for - KoXmlElement bodyElement; - bodyElement = rootElement.firstChild().toElement(); - QCOMPARE(bodyElement.nodeName(), QString("body")); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.isDocument(), false); - QCOMPARE(bodyElement.ownerDocument().isNull(), false); - QCOMPARE(bodyElement.ownerDocument() == doc, true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == rootElement, true); - QCOMPARE(bodyElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 1); - QCOMPARE(bodyElement.tagName(), QString("body")); - QCOMPARE(bodyElement.prefix().isNull(), true); - QCOMPARE(bodyElement.hasAttribute("bgcolor"), true); - QCOMPARE(bodyElement.attribute("bgcolor"), QString("#000")); - - // a shared copy of , will still have access to attribute bgcolor - KoXmlElement body2Element; - body2Element = bodyElement; - QCOMPARE(body2Element.nodeName(), QString("body")); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.isDocument(), false); - QCOMPARE(body2Element.ownerDocument().isNull(), false); - QCOMPARE(body2Element.ownerDocument() == doc, true); - QCOMPARE(body2Element == bodyElement, true); - QCOMPARE(body2Element != bodyElement, false); - QCOMPARE(body2Element.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(body2Element), 1); - QCOMPARE(body2Element.tagName(), QString("body")); - QCOMPARE(body2Element.prefix().isNull(), true); - QCOMPARE(body2Element.hasAttribute("bgcolor"), true); - QCOMPARE(body2Element.attribute("bgcolor"), QString("#000")); - - // empty element, by default constructor - KoXmlElement testElement; - QCOMPARE(testElement.nodeName(), QString()); - QCOMPARE(testElement.isNull(), true); - QCOMPARE(testElement.isElement(), false); - QCOMPARE(testElement.isDocument(), false); - QCOMPARE(testElement.ownerDocument().isNull(), true); - QCOMPARE(testElement.ownerDocument() != doc, true); - QCOMPARE(testElement == rootElement, false); - QCOMPARE(testElement != rootElement, true); - QCOMPARE(testElement.parentNode().isNull(), true); - QCOMPARE(testElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(testElement), 0); - - // check assignment operator - testElement = rootElement; - QCOMPARE(testElement.nodeName(), QString("html")); - QCOMPARE(testElement.isNull(), false); - QCOMPARE(testElement.isElement(), true); - QCOMPARE(testElement.isDocument(), false); - QCOMPARE(testElement == rootElement, true); - QCOMPARE(testElement != rootElement, false); - QCOMPARE(testElement.parentNode().isNull(), false); - QCOMPARE(testElement.parentNode().toDocument() == doc, true); - QCOMPARE(testElement.tagName(), QString("html")); - QCOMPARE(testElement.prefix().isNull(), true); - QCOMPARE(KoXml::childNodesCount(testElement), 1); - - // assigned from another empty element - testElement = KoXmlElement(); - QCOMPARE(testElement.isNull(), true); - QCOMPARE(testElement != rootElement, true); - - // assigned from - testElement = bodyElement; - QCOMPARE(testElement.isNull(), false); - QCOMPARE(testElement.isElement(), true); - QCOMPARE(testElement.isDocument(), false); - QCOMPARE(testElement.ownerDocument().isNull(), false); - QCOMPARE(testElement.ownerDocument() == doc, true); - QCOMPARE(testElement == bodyElement, true); - QCOMPARE(testElement.parentNode().isNull(), false); - QCOMPARE(testElement.tagName(), QString("body")); - QCOMPARE(testElement.prefix().isNull(), true); - QCOMPARE(testElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(testElement), 1); - - // copy constructor - KoXmlElement dummyElement(rootElement); - QCOMPARE(dummyElement.isNull(), false); - QCOMPARE(dummyElement.isElement(), true); - QCOMPARE(dummyElement.isDocument(), false); - QCOMPARE(dummyElement.ownerDocument().isNull(), false); - QCOMPARE(dummyElement.ownerDocument() == doc, true); - QCOMPARE(dummyElement == rootElement, true); - QCOMPARE(dummyElement.parentNode().isNull(), false); - QCOMPARE(dummyElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(dummyElement), 1); - QCOMPARE(dummyElement.tagName(), QString("html")); - QCOMPARE(dummyElement.prefix().isNull(), true); - - // clear() turns element to null node - dummyElement.clear(); - QCOMPARE(dummyElement.isNull(), true); - QCOMPARE(dummyElement.isElement(), false); - QCOMPARE(dummyElement.isDocument(), false); - QCOMPARE(dummyElement.ownerDocument().isNull(), true); - QCOMPARE(dummyElement.ownerDocument() == doc, false); - QCOMPARE(dummyElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(dummyElement), 0); - QCOMPARE(dummyElement == rootElement, false); - QCOMPARE(dummyElement != rootElement, true); - - // check for plain null node converted to element - KoXmlNode dummyNode; - dummyElement = dummyNode.toElement(); - QCOMPARE(dummyElement.isNull(), true); - QCOMPARE(dummyElement.isElement(), false); - QCOMPARE(dummyElement.isDocument(), false); - QCOMPARE(dummyElement.ownerDocument().isNull(), true); - QCOMPARE(dummyElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(dummyElement), 0); - QCOMPARE(dummyElement.ownerDocument() == doc, false); -} - -void TestXmlReader::testAttributes() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << "

"; - xmlstream << ""; - xmlstream << "

"; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - QCOMPARE(rootElement.tagName(), QString("p")); - QCOMPARE(rootElement.prefix().isNull(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 1); - - KoXmlElement imgElement; - imgElement = rootElement.firstChild().toElement(); - QCOMPARE(imgElement.isNull(), false); - QCOMPARE(imgElement.isElement(), true); - QCOMPARE(imgElement.tagName(), QString("img")); - QCOMPARE(imgElement.prefix().isNull(), true); - QCOMPARE(KoXml::childNodesCount(imgElement), 0); - QCOMPARE(imgElement.hasAttribute("src"), true); - QCOMPARE(imgElement.hasAttribute("width"), true); - QCOMPARE(imgElement.hasAttribute("height"), true); - QCOMPARE(imgElement.hasAttribute("non-exist"), false); - QCOMPARE(imgElement.hasAttribute("SRC"), false); - QCOMPARE(imgElement.attribute("src"), QString("foo.png")); - QCOMPARE(imgElement.attribute("width"), QString("300")); - QCOMPARE(imgElement.attribute("width").toInt(), 300); - QCOMPARE(imgElement.attribute("height"), QString("150")); - QCOMPARE(imgElement.attribute("height").toInt(), 150); - QCOMPARE(imgElement.attribute("border").isEmpty(), true); - QCOMPARE(imgElement.attribute("border", "0").toInt(), 0); - QCOMPARE(imgElement.attribute("border", "-1").toInt(), -1); - - QStringList list = KoXml::attributeNames(imgElement); - QCOMPARE(list.count(), 3); - QVERIFY(list.contains("src")); - QVERIFY(list.contains("width")); - QVERIFY(list.contains("height")); - QVERIFY(! list.contains("border")); - Q_FOREACH (QString a, list) { - QCOMPARE(imgElement.hasAttribute(a), true); - QCOMPARE(imgElement.attribute(a).isEmpty(), false); - } -} - -void TestXmlReader::testText() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << "

"; - xmlstream << "Hello "; - xmlstream << "world"; - xmlstream << "

"; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // element for

- KoXmlElement parElement; - parElement = doc.documentElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.isText(), false); - QCOMPARE(parElement.isDocument(), false); - QCOMPARE(parElement.ownerDocument().isNull(), false); - QCOMPARE(parElement.ownerDocument() == doc, true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode().toDocument() == doc, true); - QCOMPARE(parElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(parElement), 2); // and text node "Hello " - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.prefix().isNull(), true); - QCOMPARE(parElement.text(), QString("Hello world")); - - // node for "Hello" - KoXmlNode helloNode; - helloNode = parElement.firstChild(); - QCOMPARE(helloNode.nodeName(), QString("#text")); - QCOMPARE(helloNode.isNull(), false); - QCOMPARE(helloNode.isElement(), false); - QCOMPARE(helloNode.isText(), true); - QCOMPARE(helloNode.isDocument(), false); - QCOMPARE(helloNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(helloNode), 0); - - // "Hello" text - KoXmlText helloText; - helloText = helloNode.toText(); - QCOMPARE(helloText.nodeName(), QString("#text")); - QCOMPARE(helloText.isNull(), false); - QCOMPARE(helloText.isElement(), false); - QCOMPARE(helloText.isText(), true); - QCOMPARE(helloText.isDocument(), false); - QCOMPARE(helloText.data(), QString("Hello ")); - QCOMPARE(KoXml::childNodesCount(helloText), 0); - - // shared copy of the text - KoXmlText hello2Text; - hello2Text = helloText; - QCOMPARE(hello2Text.isNull(), false); - QCOMPARE(hello2Text.isElement(), false); - QCOMPARE(hello2Text.isText(), true); - QCOMPARE(hello2Text.isDocument(), false); - QCOMPARE(hello2Text.data(), QString("Hello ")); - QCOMPARE(KoXml::childNodesCount(hello2Text), 0); - - // element for - KoXmlElement boldElement; - boldElement = helloNode.nextSibling().toElement(); - QCOMPARE(boldElement.isNull(), false); - QCOMPARE(boldElement.isElement(), true); - QCOMPARE(boldElement.isText(), false); - QCOMPARE(boldElement.isDocument(), false); - QCOMPARE(boldElement.ownerDocument().isNull(), false); - QCOMPARE(boldElement.ownerDocument() == doc, true); - QCOMPARE(boldElement.parentNode().isNull(), false); - QCOMPARE(boldElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(boldElement), 1); // text node "world" - QCOMPARE(boldElement.tagName(), QString("b")); - QCOMPARE(boldElement.prefix().isNull(), true); - - // "world" text - KoXmlText worldText; - worldText = boldElement.firstChild().toText(); - QCOMPARE(worldText.isNull(), false); - QCOMPARE(worldText.isElement(), false); - QCOMPARE(worldText.isText(), true); - QCOMPARE(worldText.isDocument(), false); - QCOMPARE(worldText.data(), QString("world")); - QCOMPARE(KoXml::childNodesCount(worldText), 0); -} - -void TestXmlReader::testCDATA() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << "

"; - xmlstream << "Hello "; - xmlstream << ""; - xmlstream << "

"; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // element for

- KoXmlElement parElement; - parElement = doc.documentElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.isText(), false); - QCOMPARE(parElement.isDocument(), false); - QCOMPARE(parElement.ownerDocument().isNull(), false); - QCOMPARE(parElement.ownerDocument() == doc, true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode().toDocument() == doc, true); - QCOMPARE(parElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(parElement), 2); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.prefix().isNull(), true); - QCOMPARE(parElement.text(), QString("Hello world")); - - // node for "Hello" - KoXmlNode helloNode; - helloNode = parElement.firstChild(); - QCOMPARE(helloNode.isNull(), false); - QCOMPARE(helloNode.isElement(), false); - QCOMPARE(helloNode.isText(), true); - QCOMPARE(helloNode.isDocument(), false); - - // "Hello" text - KoXmlText helloText; - helloText = helloNode.toText(); - QCOMPARE(helloText.isNull(), false); - QCOMPARE(helloText.isElement(), false); - QCOMPARE(helloText.isText(), true); - QCOMPARE(helloText.isDocument(), false); - QCOMPARE(helloText.data(), QString("Hello ")); - - // node for CDATA "world!" - // Note: isText() is also true for CDATA - KoXmlNode worldNode; - worldNode = helloNode.nextSibling(); - QCOMPARE(worldNode.nodeName(), QString("#cdata-section")); - QCOMPARE(worldNode.isNull(), false); - QCOMPARE(worldNode.isElement(), false); - QCOMPARE(worldNode.isText(), true); - QCOMPARE(worldNode.isCDATASection(), true); - QCOMPARE(worldNode.isDocument(), false); - - // CDATA section for "world!" - // Note: isText() is also true for CDATA - KoXmlCDATASection worldCDATA; - worldCDATA = worldNode.toCDATASection(); - QCOMPARE(worldCDATA.nodeName(), QString("#cdata-section")); - QCOMPARE(worldCDATA.isNull(), false); - QCOMPARE(worldCDATA.isElement(), false); - QCOMPARE(worldCDATA.isText(), true); - QCOMPARE(worldCDATA.isCDATASection(), true); - QCOMPARE(worldCDATA.isDocument(), false); - QCOMPARE(worldCDATA.data(), QString("world")); -} - -void TestXmlReader::testDocument() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - - // empty document - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), true); - QCOMPARE(doc.lastChild().isNull(), true); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // now give something as the content - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // this document has something already - QCOMPARE(doc.nodeName(), QString("#document")); - QCOMPARE(doc.isNull(), false); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), true); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), false); - QCOMPARE(doc.lastChild().isNull(), false); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // make sure its children are fine - KoXmlElement rootElement; - rootElement = doc.firstChild().toElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.isDocument(), false); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - rootElement = doc.lastChild().toElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.isDocument(), false); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.parentNode().toDocument() == doc, true); - - // clear() converts it into null node - doc.clear(); - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), true); - QCOMPARE(doc.lastChild().isNull(), true); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // assigned from another empty document - doc = KoXmlDocument(); - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.nodeName().isEmpty(), true); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); -} - -void TestXmlReader::testDocumentType() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << ""; - xmlstream << " \n"; - xmlstream << "

\n"; - xmlstream << "

\n"; - xmlstream << ""; - xmldevice.close(); - - // empty document - KoXmlDocument doc; - QCOMPARE(doc.nodeName(), QString()); - QCOMPARE(doc.isNull(), true); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), false); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), true); - QCOMPARE(doc.lastChild().isNull(), true); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // has empty doctype - KoXmlDocumentType doctype = doc.doctype(); - QCOMPARE(doctype.nodeName(), QString()); - QCOMPARE(doctype.isNull(), true); - QCOMPARE(doctype.isElement(), false); - QCOMPARE(doctype.isDocument(), false); - QCOMPARE(doctype.isDocumentType(), false); - QCOMPARE(doctype.parentNode().isNull(), true); - QCOMPARE(doctype.firstChild().isNull(), true); - QCOMPARE(doctype.lastChild().isNull(), true); - QCOMPARE(doctype.previousSibling().isNull(), true); - QCOMPARE(doctype.nextSibling().isNull(), true); - - // now give something as the content - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // this document has something already - QCOMPARE(doc.nodeName(), QString("#document")); - QCOMPARE(doc.isNull(), false); - QCOMPARE(doc.isElement(), false); - QCOMPARE(doc.isDocument(), true); - QCOMPARE(doc.parentNode().isNull(), true); - QCOMPARE(doc.firstChild().isNull(), false); - QCOMPARE(doc.lastChild().isNull(), false); - QCOMPARE(doc.previousSibling().isNull(), true); - QCOMPARE(doc.nextSibling().isNull(), true); - - // the doctype becomes a valid one - doctype = doc.doctype(); - QCOMPARE(doctype.nodeName(), QString("html")); - QCOMPARE(doctype.name(), QString("html")); - QCOMPARE(doctype.isNull(), false); - QCOMPARE(doctype.isElement(), false); - QCOMPARE(doctype.isDocument(), false); - QCOMPARE(doctype.isDocumentType(), true); - QCOMPARE(doctype.parentNode().isNull(), false); - QCOMPARE(doctype.parentNode() == doc, true); - QCOMPARE(doctype.firstChild().isNull(), true); - QCOMPARE(doctype.lastChild().isNull(), true); - QCOMPARE(doctype.previousSibling().isNull(), true); - QCOMPARE(doctype.nextSibling().isNull(), true); -} - -void TestXmlReader::testNamespace() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - // taken from example in Qt documentation (xml.html) - xmlstream << ""; - xmlstream << ""; - xmlstream << " Practical XML"; - xmlstream << " "; - xmlstream << " "; - xmlstream << " A Namespace Called fnord"; - xmlstream << " "; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - KoXmlElement rootElement; - KoXmlElement bookElement; - KoXmlElement bookTitleElement; - KoXmlElement bookAuthorElement; - - // ------------- first without any namespace processing ----------- - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.tagName(), QString("document")); - QCOMPARE(rootElement.prefix().isNull(), true); - - bookElement = rootElement.firstChildElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isNull(), true); - QCOMPARE(bookElement.localName(), QString()); - - bookTitleElement = bookElement.firstChildElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("book:title")); - QCOMPARE(bookTitleElement.prefix().isNull(), true); - QCOMPARE(bookTitleElement.localName(), QString()); - - KoXmlNode whiteSpace = bookTitleElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - bookAuthorElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(bookAuthorElement.isNull(), false); - QCOMPARE(bookAuthorElement.isElement(), true); - QCOMPARE(bookAuthorElement.tagName(), QString("book:author")); - QCOMPARE(bookAuthorElement.prefix().isNull(), true); - QCOMPARE(bookAuthorElement.attribute("title"), QString("Ms")); - QCOMPARE(bookAuthorElement.attribute("fnord:title"), QString("Goddess")); - QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); - - // ------------- now with namespace processing ----------- - xmldevice.seek(0); // just to rewind - - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* defaultNS = "http://trolltech.com/fnord/"; - const char* bookNS = "http://trolltech.com/fnord/book/"; - const char* fnordNS = "http://trolltech.com/fnord/"; - - // - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.tagName(), QString("document")); - QCOMPARE(rootElement.prefix().isEmpty(), true); - QCOMPARE(rootElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(rootElement.localName(), QString("document")); - - // - bookElement = rootElement.firstChild().toElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isEmpty(), true); - QCOMPARE(bookElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(bookElement.localName(), QString("book")); - - // - bookTitleElement = bookElement.firstChildElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("title")); - QCOMPARE(bookTitleElement.prefix(), QString("book")); - QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookTitleElement.localName(), QString("title")); - - // another way, find it using namedItemNS() - KoXmlElement book2TitleElement; - book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title"); - //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); - QCOMPARE(book2TitleElement == bookTitleElement, true); - QCOMPARE(book2TitleElement.isNull(), false); - QCOMPARE(book2TitleElement.isElement(), true); - QCOMPARE(book2TitleElement.tagName(), QString("title")); - - whiteSpace = bookTitleElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // - bookAuthorElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(bookAuthorElement.isNull(), false); - QCOMPARE(bookAuthorElement.isElement(), true); - QCOMPARE(bookAuthorElement.tagName(), QString("author")); - QCOMPARE(bookAuthorElement.prefix(), QString("book")); - QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookAuthorElement.localName(), QString("author")); - - // another way, find it using namedItemNS() - KoXmlElement book2AuthorElement; - book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author"); - //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); - QCOMPARE(book2AuthorElement == bookAuthorElement, true); - QCOMPARE(book2AuthorElement.isNull(), false); - QCOMPARE(book2AuthorElement.isElement(), true); - QCOMPARE(book2AuthorElement.tagName(), QString("author")); - - // attributes in - // Note: with namespace processing, attribute's prefix is taken out and - // hence "fnord:title" will simply override "title" - // and searching attribute with prefix will give no result - QCOMPARE(bookAuthorElement.hasAttribute("title"), true); - QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttribute("name"), true); - QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess")); - QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true); - QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); - - // attributes in , with NS family of functions - // those without prefix are not accessible at all, because they do not belong - // to any namespace at all. - // Note: default namespace does not apply to attribute names! - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess")); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString()); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess")); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true); -} - -// mostly similar to testNamespace above, but parse from a QString -void TestXmlReader::testParseQString() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QString xmlText; - xmlText += ""; - xmlText += ""; - xmlText += " Practical XML"; - xmlText += " "; - xmlText += " "; - xmlText += " A Namespace Called fnord"; - xmlText += " "; - xmlText += ""; - xmlText += ""; - - KoXmlDocument doc; - KoXmlElement rootElement; - KoXmlElement bookElement; - KoXmlElement bookTitleElement; - KoXmlElement bookAuthorElement; - - QCOMPARE(doc.setContent(xmlText, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* defaultNS = "http://trolltech.com/fnord/"; - const char* bookNS = "http://trolltech.com/fnord/book/"; - const char* fnordNS = "http://trolltech.com/fnord/"; - - // - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.tagName(), QString("document")); - QCOMPARE(rootElement.prefix().isEmpty(), true); - QCOMPARE(rootElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(rootElement.localName(), QString("document")); - - // - bookElement = rootElement.firstChild().toElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isEmpty(), true); - QCOMPARE(bookElement.namespaceURI(), QString(defaultNS)); - QCOMPARE(bookElement.localName(), QString("book")); - - // - bookTitleElement = bookElement.firstChildElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("title")); - QCOMPARE(bookTitleElement.prefix(), QString("book")); - QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookTitleElement.localName(), QString("title")); - - // another way, find it using namedItemNS() - KoXmlElement book2TitleElement; - book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title"); - //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); - QCOMPARE(book2TitleElement == bookTitleElement, true); - QCOMPARE(book2TitleElement.isNull(), false); - QCOMPARE(book2TitleElement.isElement(), true); - QCOMPARE(book2TitleElement.tagName(), QString("title")); - - KoXmlNode whiteSpace = bookTitleElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // - bookAuthorElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(bookAuthorElement.isNull(), false); - QCOMPARE(bookAuthorElement.isElement(), true); - QCOMPARE(bookAuthorElement.tagName(), QString("author")); - QCOMPARE(bookAuthorElement.prefix(), QString("book")); - QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS)); - QCOMPARE(bookAuthorElement.localName(), QString("author")); - - // another way, find it using namedItemNS() - KoXmlElement book2AuthorElement; - book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author"); - //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); - QCOMPARE(book2AuthorElement == bookAuthorElement, true); - QCOMPARE(book2AuthorElement.isNull(), false); - QCOMPARE(book2AuthorElement.isElement(), true); - QCOMPARE(book2AuthorElement.tagName(), QString("author")); - - // attributes in - // Note: with namespace processing, attribute's prefix is taken out and - // hence "fnord:title" will simply override "title" - // and searching attribute with prefix will give no result - QCOMPARE(bookAuthorElement.hasAttribute("title"), true); - QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttribute("name"), true); - QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess")); - QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true); - QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); - - // attributes in , with NS family of functions - // those without prefix are not accessible at all, because they do not belong - // to any namespace at all. - // Note: default namespace does not apply to attribute names! - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess")); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString()); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess")); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false); - - QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false); - QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false); - - QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true); - QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true); -} - -void TestXmlReader::testUnload() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement earthElement; - earthElement = doc.documentElement().toElement(); - QCOMPARE(earthElement.isNull(), false); - QCOMPARE(earthElement.isElement(), true); - QCOMPARE(earthElement.parentNode().isNull(), false); - QCOMPARE(earthElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(earthElement), 2); - QCOMPARE(earthElement.tagName(), QString("earth")); - QCOMPARE(earthElement.prefix().isNull(), true); - - // this ensures that all child nodes of are loaded - earthElement.firstChild(); - - // explicitly unload all child nodes of - KoXml::unload(earthElement); - - // we should get the correct first child - KoXmlElement continentsElement = earthElement.firstChild().toElement(); - QCOMPARE(continentsElement.nodeName(), QString("continents")); - QCOMPARE(continentsElement.isNull(), false); - QCOMPARE(continentsElement.isElement(), true); - QCOMPARE(continentsElement.isText(), false); - QCOMPARE(continentsElement.isDocument(), false); - QCOMPARE(continentsElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(continentsElement), 6); - - // let us unload everything again - KoXml::unload(earthElement); - - // we should get the correct last child - KoXmlElement oceansElement = earthElement.lastChild().toElement(); - QCOMPARE(oceansElement.nodeName(), QString("oceans")); - QCOMPARE(oceansElement.isNull(), false); - QCOMPARE(oceansElement.isElement(), true); - QCOMPARE(oceansElement.isText(), false); - QCOMPARE(oceansElement.isDocument(), false); - QCOMPARE(oceansElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(continentsElement), 6); -} - -void TestXmlReader::testSimpleXML() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 11); - QCOMPARE(rootElement.tagName(), QString("solarsystem")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // node - KoXmlNode firstPlanetNode; - firstPlanetNode = rootElement.firstChildElement(); - QCOMPARE(firstPlanetNode.isNull(), false); - QCOMPARE(firstPlanetNode.isElement(), true); - QCOMPARE(firstPlanetNode.nextSibling().isNull(), false); - QCOMPARE(firstPlanetNode.previousSibling().isNull(), false); - QCOMPARE(firstPlanetNode.parentNode().isNull(), false); - QCOMPARE(firstPlanetNode.parentNode() == rootElement, true); - QCOMPARE(firstPlanetNode.parentNode() != rootElement, false); - QCOMPARE(firstPlanetNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0); - QCOMPARE(firstPlanetNode.firstChild().isNull(), true); - QCOMPARE(firstPlanetNode.lastChild().isNull(), true); - - // element - KoXmlElement firstPlanetElement; - firstPlanetElement = firstPlanetNode.toElement(); - QCOMPARE(firstPlanetElement.isNull(), false); - QCOMPARE(firstPlanetElement.isElement(), true); - QCOMPARE(firstPlanetElement.parentNode().isNull(), false); - QCOMPARE(firstPlanetElement.parentNode() == rootElement, true); - QCOMPARE(firstPlanetElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0); - QCOMPARE(firstPlanetElement.firstChild().isNull(), true); - QCOMPARE(firstPlanetElement.lastChild().isNull(), true); - QCOMPARE(firstPlanetElement.tagName(), QString("mercurius")); - QCOMPARE(firstPlanetElement.prefix().isNull(), true); - - KoXmlNode whiteSpace = firstPlanetElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // node - KoXmlNode secondPlanetNode; - secondPlanetNode = whiteSpace.nextSibling(); - QCOMPARE(secondPlanetNode.isNull(), false); - QCOMPARE(secondPlanetNode.isElement(), true); - QCOMPARE(secondPlanetNode.nextSibling().isNull(), false); - QCOMPARE(secondPlanetNode.previousSibling().isNull(), false); - QCOMPARE(secondPlanetNode.previousSibling() == whiteSpace, true); - QCOMPARE(secondPlanetNode.parentNode().isNull(), false); - QCOMPARE(secondPlanetNode.parentNode() == rootElement, true); - QCOMPARE(secondPlanetNode.parentNode() != rootElement, false); - QCOMPARE(secondPlanetNode.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0); - QCOMPARE(secondPlanetNode.firstChild().isNull(), true); - QCOMPARE(secondPlanetNode.lastChild().isNull(), true); - - // element - KoXmlElement secondPlanetElement; - secondPlanetElement = secondPlanetNode.toElement(); - QCOMPARE(secondPlanetElement.isNull(), false); - QCOMPARE(secondPlanetElement.isElement(), true); - QCOMPARE(secondPlanetElement.nextSibling().isNull(), false); - QCOMPARE(secondPlanetElement.previousSibling().isNull(), false); - QCOMPARE(secondPlanetElement.previousSibling() == whiteSpace, true); - QCOMPARE(secondPlanetElement.parentNode().isNull(), false); - QCOMPARE(secondPlanetElement.parentNode() == rootElement, true); - QCOMPARE(secondPlanetElement.parentNode() != rootElement, false); - QCOMPARE(secondPlanetElement.hasChildNodes(), false); - QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0); - QCOMPARE(secondPlanetElement.firstChild().isNull(), true); - QCOMPARE(secondPlanetElement.lastChild().isNull(), true); - QCOMPARE(secondPlanetElement.tagName(), QString("venus")); - QCOMPARE(secondPlanetElement.prefix().isNull(), true); -} - -void TestXmlReader::testRootError() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - // multiple root nodes are not valid ! - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false); - QCOMPARE(errorMsg.isEmpty(), false); - QCOMPARE(errorMsg, QString("Extra content at end of document.")); - QCOMPARE(errorLine, 1); - QCOMPARE(errorColumn, 21); -} - -void TestXmlReader::testMismatchedTag() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false); - QCOMPARE(errorMsg.isEmpty(), false); - QCOMPARE(errorMsg, QString("Opening and ending tag mismatch.")); - QCOMPARE(errorLine, 1); - QCOMPARE(errorColumn, 11); -} - -void TestXmlReader::testConvertQDomDocument() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << "

The best place

"; - xmlstream << " \n"; - xmlstream << "
\n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << "
"; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 11); - QCOMPARE(rootElement.tagName(), QString("solarsystem")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // now test converting KoXmlDocument to QDomDocument - QDomDocument universeDoc = KoXml::asQDomDocument(doc); - - // - QDomElement solarSystemElement = universeDoc.documentElement(); - QCOMPARE(solarSystemElement.isNull(), false); - QCOMPARE(solarSystemElement.isElement(), true); - QCOMPARE(solarSystemElement.parentNode().isNull(), false); - QCOMPARE(solarSystemElement.hasChildNodes(), true); - QCOMPARE(solarSystemElement.tagName(), QString("solarsystem")); - QCOMPARE(solarSystemElement.prefix().isNull(), true); - - // - QDomElement earthElement = solarSystemElement.namedItem("earth").toElement(); - QCOMPARE(earthElement.isNull(), false); - QCOMPARE(earthElement.isElement(), true); - QCOMPARE(earthElement.parentNode().isNull(), false); - QCOMPARE(earthElement.hasAttribute("habitable"), true); - QCOMPARE(earthElement.hasChildNodes(), true); - QCOMPARE(earthElement.tagName(), QString("earth")); - QCOMPARE(earthElement.prefix().isNull(), true); - - //

in - QDomNode placeNode = earthElement.firstChild(); - QCOMPARE(placeNode.isNull(), false); - QCOMPARE(placeNode.isElement(), true); - QCOMPARE(placeNode.toElement().text(), QString("The best place")); - QCOMPARE(placeNode.nextSibling().isNull(), false); - QCOMPARE(placeNode.previousSibling().isNull(), true); - QCOMPARE(placeNode.parentNode().isNull(), false); - QCOMPARE(placeNode.parentNode() == earthElement, true); - QCOMPARE(placeNode.hasChildNodes(), true); - QCOMPARE(placeNode.childNodes().count(), 1); - - //printf("Result:\n%s\n\n", qPrintable(universeDoc.toString())); -} - -void TestXmlReader::testConvertQDomElement() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - xmlstream << ""; - xmlstream << " "; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << "

The best place

"; - xmlstream << " \n"; - xmlstream << "
\n"; - xmlstream << " \n"; - xmlstream << " \n"; - xmlstream << "
"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - // - KoXmlElement rootElement; - rootElement = doc.documentElement(); - QCOMPARE(rootElement.isNull(), false); - QCOMPARE(rootElement.isElement(), true); - QCOMPARE(rootElement.parentNode().isNull(), false); - QCOMPARE(rootElement.hasChildNodes(), true); - QCOMPARE(KoXml::childNodesCount(rootElement), 2); - QCOMPARE(rootElement.tagName(), QString("universe")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // now test converting KoXmlElement to QDomElement - QDomDocument solarDoc; - KoXml::asQDomElement(solarDoc, rootElement.firstChildElement()); - - // - QDomElement solarSystemElement = solarDoc.documentElement(); - QCOMPARE(solarSystemElement.isNull(), false); - QCOMPARE(solarSystemElement.isElement(), true); - QCOMPARE(solarSystemElement.parentNode().isNull(), false); - QCOMPARE(solarSystemElement.hasChildNodes(), true); - QCOMPARE(solarSystemElement.tagName(), QString("solarsystem")); - QCOMPARE(solarSystemElement.prefix().isNull(), true); - - // - QDomElement earthElement = solarSystemElement.namedItem("earth").toElement(); - QCOMPARE(earthElement.isNull(), false); - QCOMPARE(earthElement.isElement(), true); - QCOMPARE(earthElement.parentNode().isNull(), false); - QCOMPARE(earthElement.hasAttribute("habitable"), true); - QCOMPARE(earthElement.hasChildNodes(), true); - QCOMPARE(earthElement.tagName(), QString("earth")); - QCOMPARE(earthElement.prefix().isNull(), true); - - //

in - QDomNode placeNode = earthElement.firstChild(); - QCOMPARE(placeNode.isNull(), false); - QCOMPARE(placeNode.isElement(), true); - QCOMPARE(placeNode.toElement().text(), QString("The best place")); - QCOMPARE(placeNode.nextSibling().isNull(), false); - QCOMPARE(placeNode.previousSibling().isNull(), true); - QCOMPARE(placeNode.parentNode().isNull(), false); - QCOMPARE(placeNode.parentNode() == earthElement, true); - QCOMPARE(placeNode.hasChildNodes(), true); - QCOMPARE(placeNode.childNodes().count(), 1); - - //printf("Result:\n%s\n\n", qPrintable(universeDoc.toString())); -} - -void TestXmlReader::testSimpleOpenDocumentText() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - - // content.xml from a simple OpenDocument text - // it has only paragraph "Hello, world!" - // automatic styles, declarations and unnecessary namespaces are omitted. - xmlstream << ""; - xmlstream << ""; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " Hello, world!"; - xmlstream << " "; - xmlstream << " "; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.parentNode().isNull(), false); - QCOMPARE(contentElement.parentNode().toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(contentElement), 4); - QCOMPARE(contentElement.firstChild().isNull(), false); - QCOMPARE(contentElement.lastChild().isNull(), false); - QCOMPARE(contentElement.previousSibling().isNull(), false); - QCOMPARE(contentElement.nextSibling().isNull(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true); - QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0")); - - // - KoXmlElement stylesElement; - stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles"); - QCOMPARE(stylesElement.isNull(), false); - QCOMPARE(stylesElement.isElement(), true); - QCOMPARE(stylesElement.parentNode().isNull(), false); - QCOMPARE(stylesElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(stylesElement), 0); - QCOMPARE(stylesElement.firstChild().isNull(), true); - QCOMPARE(stylesElement.lastChild().isNull(), true); - QCOMPARE(stylesElement.previousSibling().isNull(), false); - QCOMPARE(stylesElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(stylesElement.nextSibling().isNull(), false); - QCOMPARE(stylesElement.localName(), QString("automatic-styles")); - - // also same , but without namedItemNS - KoXmlNode styles2Element; - styles2Element = contentElement.firstChildElement(); - QCOMPARE(styles2Element.isNull(), false); - QCOMPARE(styles2Element.isElement(), true); - QCOMPARE(styles2Element.parentNode().isNull(), false); - QCOMPARE(styles2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(styles2Element), 0); - QCOMPARE(styles2Element.firstChild().isNull(), true); - QCOMPARE(styles2Element.lastChild().isNull(), true); - QCOMPARE(styles2Element.previousSibling().isNull(), false); - QCOMPARE(styles2Element.previousSibling().previousSibling().isNull(), true); - QCOMPARE(styles2Element.nextSibling().isNull(), false); - QCOMPARE(styles2Element.localName(), QString("automatic-styles")); - - // - KoXmlElement bodyElement; - bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body"); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 3); - QCOMPARE(bodyElement.firstChild().isNull(), false); - QCOMPARE(bodyElement.lastChild().isNull(), false); - QCOMPARE(bodyElement.previousSibling().isNull(), false); - QCOMPARE(bodyElement.nextSibling().isNull(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - KoXmlNode whiteSpace = stylesElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // also same , but without namedItemNS - KoXmlElement body2Element = whiteSpace.nextSibling().toElement(); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.parentNode().isNull(), false); - QCOMPARE(body2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(body2Element), 3); - QCOMPARE(body2Element.firstChild().isNull(), false); - QCOMPARE(body2Element.lastChild().isNull(), false); - QCOMPARE(body2Element.previousSibling().isNull(), false); - QCOMPARE(body2Element.nextSibling().isNull(), true); - QCOMPARE(body2Element.localName(), QString("body")); - - // - KoXmlElement textElement; - textElement = KoXml::namedItemNS(bodyElement, officeNS, "text"); - QCOMPARE(textElement.isNull(), false); - QCOMPARE(textElement.isElement(), true); - QCOMPARE(textElement.parentNode().isNull(), false); - QCOMPARE(textElement.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(textElement), 3); - QCOMPARE(textElement.firstChild().isNull(), false); - QCOMPARE(textElement.lastChild().isNull(), false); - QCOMPARE(textElement.previousSibling().isNull(), false); - QCOMPARE(textElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(textElement.nextSibling().isNull(), false); - QCOMPARE(textElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(textElement.localName(), QString("text")); - - // the same , but without namedItemNS - KoXmlElement text2Element; - text2Element = bodyElement.firstChildElement(); - QCOMPARE(text2Element.isNull(), false); - QCOMPARE(text2Element.isElement(), true); - QCOMPARE(text2Element.parentNode().isNull(), false); - QCOMPARE(text2Element.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(text2Element), 3); - QCOMPARE(text2Element.firstChild().isNull(), false); - QCOMPARE(text2Element.lastChild().isNull(), false); - QCOMPARE(text2Element.previousSibling().isNull(), false); - QCOMPARE(text2Element.previousSibling().previousSibling().isNull(), true); - QCOMPARE(text2Element.nextSibling().isNull(), false); - QCOMPARE(text2Element.nextSibling().nextSibling().isNull(), true); - QCOMPARE(text2Element.localName(), QString("text")); - - // - KoXmlElement parElement; - parElement = textElement.firstChildElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode() == textElement, true); - QCOMPARE(KoXml::childNodesCount(parElement), 1); - QCOMPARE(parElement.firstChild().isNull(), false); - QCOMPARE(parElement.lastChild().isNull(), false); - QCOMPARE(parElement.previousSibling().isNull(), false); - QCOMPARE(parElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(parElement.nextSibling().isNull(), false); - QCOMPARE(parElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.text(), QString("Hello, world!")); - QCOMPARE(parElement.attributeNS(QString(textNS), "style-name", ""), QString("Standard")); -} - -void TestXmlReader::testWhitespace() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - - // content.xml for testing paragraphs with whitespace - /* The list of elements for which whitespace should be preserved can be - obtained from the Relax NG schema with these commands: - - cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \ - -N s="http://relaxng.org/ns/structure/1.0" -t -m '//s:text' \ - -v '../@name' -n |grep : - cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \ - -N s="http://relaxng.org/ns/structure/1.0" \ - -t -m "//s:ref[@name='paragraph-content']" -v '../../@name' -n |grep : - */ - - xmlstream << ""; - xmlstream << ""; - xmlstream << " "; - xmlstream << " "; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement p1; - p1 = doc.documentElement().firstChildElement(); - QCOMPARE(p1.isNull(), false); - QCOMPARE(p1.isElement(), true); - QCOMPARE(KoXml::childNodesCount(p1), 1); - - KoXmlNode whiteSpace = p1.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - KoXmlElement p2; - p2 = whiteSpace.nextSibling().toElement(); - QCOMPARE(p2.isNull(), false); - QCOMPARE(p2.isElement(), true); - QCOMPARE(KoXml::childNodesCount(p2), 3); -} - -void TestXmlReader::testSimpleOpenDocumentSpreadsheet() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - - // content.xml from a simple OpenDocument spreadsheet - // the document has three worksheets, the last two are empty. - // on the first sheet, cell A1 contains the text "Hello, world". - // automatic styles, font declarations and unnecessary namespaces are omitted. - - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "Hello, world"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; - QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.parentNode().isNull(), false); - QCOMPARE(contentElement.parentNode().toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(contentElement), 1); - QCOMPARE(contentElement.firstChild().isNull(), false); - QCOMPARE(contentElement.lastChild().isNull(), false); - QCOMPARE(contentElement.previousSibling().isNull(), false); - QCOMPARE(contentElement.nextSibling().isNull(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - - // - KoXmlElement bodyElement; - bodyElement = contentElement.firstChild().toElement(); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 1); - QCOMPARE(bodyElement.firstChild().isNull(), false); - QCOMPARE(bodyElement.lastChild().isNull(), false); - QCOMPARE(bodyElement.previousSibling().isNull(), true); - QCOMPARE(bodyElement.nextSibling().isNull(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChild().toElement(); - QCOMPARE(spreadsheetElement.isNull(), false); - QCOMPARE(spreadsheetElement.isElement(), true); - QCOMPARE(spreadsheetElement.parentNode().isNull(), false); - QCOMPARE(spreadsheetElement.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(spreadsheetElement), 3); - QCOMPARE(spreadsheetElement.firstChild().isNull(), false); - QCOMPARE(spreadsheetElement.lastChild().isNull(), false); - QCOMPARE(spreadsheetElement.previousSibling().isNull(), true); - QCOMPARE(spreadsheetElement.nextSibling().isNull(), true); - QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); - - // for Sheet1 - KoXmlElement sheet1Element; - sheet1Element = spreadsheetElement.firstChild().toElement(); - QCOMPARE(sheet1Element.isNull(), false); - QCOMPARE(sheet1Element.isElement(), true); - QCOMPARE(sheet1Element.parentNode().isNull(), false); - QCOMPARE(sheet1Element.parentNode() == spreadsheetElement, true); - QCOMPARE(KoXml::childNodesCount(sheet1Element), 2); - QCOMPARE(sheet1Element.firstChild().isNull(), false); - QCOMPARE(sheet1Element.lastChild().isNull(), false); - QCOMPARE(sheet1Element.previousSibling().isNull(), true); - QCOMPARE(sheet1Element.nextSibling().isNull(), false); - QCOMPARE(sheet1Element.tagName(), QString("table")); - QCOMPARE(sheet1Element.hasAttributeNS(tableNS, "name"), true); - QCOMPARE(sheet1Element.attributeNS(tableNS, "name", ""), QString("Sheet1")); - QCOMPARE(sheet1Element.attributeNS(tableNS, "style-name", ""), QString("ta1")); - QCOMPARE(sheet1Element.attributeNS(tableNS, "print", ""), QString("false")); - - // KoXml::load( sheet1Element, 100 ); - - // - KoXmlElement columnElement; - columnElement = sheet1Element.firstChild().toElement(); - QCOMPARE(columnElement.isNull(), false); - QCOMPARE(columnElement.isElement(), true); - QCOMPARE(columnElement.parentNode().isNull(), false); - QCOMPARE(columnElement.parentNode() == sheet1Element, true); - QCOMPARE(KoXml::childNodesCount(columnElement), 0); - QCOMPARE(columnElement.firstChild().isNull(), true); - QCOMPARE(columnElement.lastChild().isNull(), true); - QCOMPARE(columnElement.previousSibling().isNull(), true); - QCOMPARE(columnElement.nextSibling().isNull(), false); - QCOMPARE(columnElement.tagName(), QString("table-column")); - QCOMPARE(columnElement.attributeNS(tableNS, "style-name", ""), QString("co1")); - QCOMPARE(columnElement.attributeNS(tableNS, "default-cell-style-name", ""), QString("Default")); - - // - KoXmlElement rowElement; - rowElement = columnElement.nextSibling().toElement(); - QCOMPARE(rowElement.isNull(), false); - QCOMPARE(rowElement.isElement(), true); - QCOMPARE(rowElement.parentNode().isNull(), false); - QCOMPARE(rowElement.parentNode() == sheet1Element, true); - QCOMPARE(KoXml::childNodesCount(rowElement), 1); - QCOMPARE(rowElement.firstChild().isNull(), false); - QCOMPARE(rowElement.lastChild().isNull(), false); - QCOMPARE(rowElement.previousSibling().isNull(), false); - QCOMPARE(rowElement.nextSibling().isNull(), true); - QCOMPARE(rowElement.tagName(), QString("table-row")); - QCOMPARE(rowElement.attributeNS(tableNS, "style-name", ""), QString("ro1")); - - // - KoXmlElement cellElement; - cellElement = rowElement.firstChild().toElement(); - QCOMPARE(cellElement.isNull(), false); - QCOMPARE(cellElement.isElement(), true); - QCOMPARE(cellElement.parentNode().isNull(), false); - QCOMPARE(cellElement.parentNode() == rowElement, true); - QCOMPARE(KoXml::childNodesCount(cellElement), 1); - QCOMPARE(cellElement.firstChild().isNull(), false); - QCOMPARE(cellElement.lastChild().isNull(), false); - QCOMPARE(cellElement.previousSibling().isNull(), true); - QCOMPARE(cellElement.nextSibling().isNull(), true); - QCOMPARE(cellElement.tagName(), QString("table-cell")); - QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string")); - - // - KoXmlElement parElement; - parElement = cellElement.firstChild().toElement(); - QCOMPARE(parElement.isNull(), false); - QCOMPARE(parElement.isElement(), true); - QCOMPARE(parElement.parentNode().isNull(), false); - QCOMPARE(parElement.parentNode() == cellElement, true); - QCOMPARE(KoXml::childNodesCount(parElement), 1); - QCOMPARE(parElement.firstChild().isNull(), false); - QCOMPARE(parElement.lastChild().isNull(), false); - QCOMPARE(parElement.previousSibling().isNull(), true); - QCOMPARE(parElement.nextSibling().isNull(), true); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.text(), QString("Hello, world")); - - // for Sheet2 - KoXmlElement sheet2Element; - sheet2Element = sheet1Element.nextSibling().toElement(); - QCOMPARE(sheet2Element.isNull(), false); - QCOMPARE(sheet2Element.isElement(), true); - QCOMPARE(sheet2Element.parentNode().isNull(), false); - QCOMPARE(sheet2Element.parentNode() == spreadsheetElement, true); - QCOMPARE(KoXml::childNodesCount(sheet2Element), 2); - QCOMPARE(sheet2Element.firstChild().isNull(), false); - QCOMPARE(sheet2Element.lastChild().isNull(), false); - QCOMPARE(sheet2Element.previousSibling().isNull(), false); - QCOMPARE(sheet2Element.nextSibling().isNull(), false); - QCOMPARE(sheet2Element.tagName(), QString("table")); - - // for Sheet3 - KoXmlElement sheet3Element; - sheet3Element = sheet2Element.nextSibling().toElement(); - QCOMPARE(sheet3Element.isNull(), false); - QCOMPARE(sheet3Element.isElement(), true); - QCOMPARE(sheet3Element.parentNode().isNull(), false); - QCOMPARE(sheet3Element.parentNode() == spreadsheetElement, true); - QCOMPARE(KoXml::childNodesCount(sheet3Element), 2); - QCOMPARE(sheet3Element.firstChild().isNull(), false); - QCOMPARE(sheet3Element.lastChild().isNull(), false); - QCOMPARE(sheet3Element.previousSibling().isNull(), false); - QCOMPARE(sheet3Element.nextSibling().isNull(), true); - QCOMPARE(sheet3Element.tagName(), QString("table")); -} - -void TestXmlReader::testSimpleOpenDocumentPresentation() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - // content.xml from a simple OpenDocument presentation - // styles, declarations and unnecessary namespaces are omitted - // the first page is "Title" and has two text boxes - // the second page is - - xmlstream << ""; - xmlstream << ""; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " Foobar"; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " Foo"; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << " "; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - const char* drawNS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; - const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - const char* presentationNS = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"; - const char* svgNS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.parentNode().isNull(), false); - QCOMPARE(contentElement.parentNode().toDocument() == doc, true); - QCOMPARE(KoXml::childNodesCount(contentElement), 6); - QCOMPARE(contentElement.firstChild().isNull(), false); - QCOMPARE(contentElement.lastChild().isNull(), false); - QCOMPARE(contentElement.previousSibling().isNull(), false); - QCOMPARE(contentElement.nextSibling().isNull(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true); - QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0")); - - // - KoXmlElement scriptsElement; - scriptsElement = KoXml::namedItemNS(contentElement, officeNS, "scripts"); - QCOMPARE(scriptsElement.isNull(), false); - QCOMPARE(scriptsElement.isElement(), true); - QCOMPARE(scriptsElement.parentNode().isNull(), false); - QCOMPARE(scriptsElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(scriptsElement), 0); - QCOMPARE(scriptsElement.firstChild().isNull(), true); - QCOMPARE(scriptsElement.lastChild().isNull(), true); - QCOMPARE(scriptsElement.previousSibling().isNull(), false); - QCOMPARE(scriptsElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(scriptsElement.nextSibling().isNull(), false); - QCOMPARE(scriptsElement.localName(), QString("scripts")); - - // - KoXmlElement stylesElement; - stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles"); - QCOMPARE(stylesElement.isNull(), false); - QCOMPARE(stylesElement.isElement(), true); - QCOMPARE(stylesElement.parentNode().isNull(), false); - QCOMPARE(stylesElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(stylesElement), 0); - QCOMPARE(stylesElement.firstChild().isNull(), true); - QCOMPARE(stylesElement.lastChild().isNull(), true); - QCOMPARE(stylesElement.previousSibling().isNull(), false); - QCOMPARE(stylesElement.nextSibling().isNull(), false); - QCOMPARE(stylesElement.localName(), QString("automatic-styles")); - - KoXmlNode whiteSpace = scriptsElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // also same , but without namedItemNS - KoXmlNode styles2Element; - styles2Element = whiteSpace.nextSibling().toElement(); - QCOMPARE(styles2Element.isNull(), false); - QCOMPARE(styles2Element.isElement(), true); - QCOMPARE(styles2Element.parentNode().isNull(), false); - QCOMPARE(styles2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(styles2Element), 0); - QCOMPARE(styles2Element.firstChild().isNull(), true); - QCOMPARE(styles2Element.lastChild().isNull(), true); - QCOMPARE(styles2Element.previousSibling().isNull(), false); - QCOMPARE(styles2Element.nextSibling().isNull(), false); - QCOMPARE(styles2Element.localName(), QString("automatic-styles")); - - // - KoXmlElement bodyElement; - bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body"); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.parentNode().isNull(), false); - QCOMPARE(bodyElement.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(bodyElement), 3); - QCOMPARE(bodyElement.firstChild().isNull(), false); - QCOMPARE(bodyElement.lastChild().isNull(), false); - QCOMPARE(bodyElement.previousSibling().isNull(), false); - QCOMPARE(bodyElement.nextSibling().isNull(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - whiteSpace = stylesElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // also same , but without namedItemNS - KoXmlElement body2Element; - body2Element = whiteSpace.nextSibling().toElement(); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.parentNode().isNull(), false); - QCOMPARE(body2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(body2Element), 3); - QCOMPARE(body2Element.firstChild().isNull(), false); - QCOMPARE(body2Element.lastChild().isNull(), false); - QCOMPARE(body2Element.previousSibling().isNull(), false); - QCOMPARE(body2Element.nextSibling().isNull(), true); - QCOMPARE(body2Element.localName(), QString("body")); - - // - KoXmlElement presentationElement; - presentationElement = KoXml::namedItemNS(bodyElement, officeNS, "presentation"); - QCOMPARE(presentationElement.isNull(), false); - QCOMPARE(presentationElement.isElement(), true); - QCOMPARE(presentationElement.parentNode().isNull(), false); - QCOMPARE(presentationElement.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(presentationElement), 5); - QCOMPARE(presentationElement.firstChild().isNull(), false); - QCOMPARE(presentationElement.lastChild().isNull(), false); - QCOMPARE(presentationElement.previousSibling().isNull(), false); - QCOMPARE(presentationElement.previousSibling().isText(), true); - QCOMPARE(presentationElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(presentationElement.nextSibling().isNull(), false); - QCOMPARE(presentationElement.nextSibling().isText(), true); - QCOMPARE(presentationElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(presentationElement.localName(), QString("presentation")); - - // the same , but without namedItemNS - KoXmlElement presentation2Element; - presentation2Element = bodyElement.firstChildElement(); - QCOMPARE(presentation2Element.isNull(), false); - QCOMPARE(presentation2Element.isElement(), true); - QCOMPARE(presentation2Element.parentNode().isNull(), false); - QCOMPARE(presentation2Element.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(presentation2Element), 5); - QCOMPARE(presentation2Element.firstChild().isNull(), false); - QCOMPARE(presentation2Element.lastChild().isNull(), false); - QCOMPARE(presentation2Element.previousSibling().isNull(), false); - QCOMPARE(presentation2Element.previousSibling().isText(), true); - QCOMPARE(presentation2Element.previousSibling().previousSibling().isNull(), true); - QCOMPARE(presentation2Element.nextSibling().isNull(), false); - QCOMPARE(presentation2Element.nextSibling().isText(), true); - QCOMPARE(presentation2Element.nextSibling().nextSibling().isNull(), true); - QCOMPARE(presentation2Element.localName(), QString("presentation")); - - // for "Title" - KoXmlElement titlePageElement; - titlePageElement = presentationElement.firstChildElement(); - QCOMPARE(titlePageElement.isNull(), false); - QCOMPARE(titlePageElement.isElement(), true); - QCOMPARE(titlePageElement.parentNode().isNull(), false); - QCOMPARE(titlePageElement.parentNode() == presentationElement, true); - QCOMPARE(KoXml::childNodesCount(titlePageElement), 7); - QCOMPARE(titlePageElement.firstChild().isNull(), false); - QCOMPARE(titlePageElement.lastChild().isNull(), false); - QCOMPARE(titlePageElement.previousSibling().isNull(), false); - QCOMPARE(titlePageElement.previousSibling().isText(), true); - QCOMPARE(titlePageElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(titlePageElement.nextSibling().isNull(), false); - QCOMPARE(titlePageElement.localName(), QString("page")); - QCOMPARE(titlePageElement.attributeNS(drawNS, "name", ""), QString("Title")); - QCOMPARE(titlePageElement.attributeNS(drawNS, "style-name", ""), QString("dp1")); - QCOMPARE(titlePageElement.attributeNS(drawNS, "master-page-name", ""), QString("lyt-cool")); - QCOMPARE(titlePageElement.attributeNS(presentationNS, - "presentation-page-layout-name", ""), QString("AL1T0")); - - // for the title frame - KoXmlElement titleFrameElement; - titleFrameElement = titlePageElement.firstChildElement(); - QCOMPARE(titleFrameElement.isNull(), false); - QCOMPARE(titleFrameElement.isElement(), true); - QCOMPARE(titleFrameElement.parentNode().isNull(), false); - QCOMPARE(titleFrameElement.parentNode() == titlePageElement, true); - QCOMPARE(KoXml::childNodesCount(titleFrameElement), 3); - QCOMPARE(titleFrameElement.firstChild().isNull(), false); - QCOMPARE(titleFrameElement.lastChild().isNull(), false); - QCOMPARE(titleFrameElement.previousSibling().isNull(), false); - QCOMPARE(titleFrameElement.previousSibling().isText(), true); - QCOMPARE(titleFrameElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(titleFrameElement.nextSibling().isNull(), false); - QCOMPARE(titleFrameElement.localName(), QString("frame")); - QCOMPARE(titleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr1")); - QCOMPARE(titleFrameElement.attributeNS(presentationNS, "class", ""), QString("title")); - QCOMPARE(titleFrameElement.attributeNS(presentationNS, "user-transformed", ""), QString("true")); - QCOMPARE(titleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P2")); - QCOMPARE(titleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "height", ""), QString("3.508cm")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm")); - QCOMPARE(titleFrameElement.attributeNS(svgNS, "y", ""), QString("1.543cm")); - - // of the title frame - KoXmlElement titleBoxElement; - titleBoxElement = titleFrameElement.firstChildElement(); - QCOMPARE(titleBoxElement.isNull(), false); - QCOMPARE(titleBoxElement.isElement(), true); - QCOMPARE(titleBoxElement.parentNode().isNull(), false); - QCOMPARE(titleBoxElement.parentNode() == titleFrameElement, true); - QCOMPARE(KoXml::childNodesCount(titleBoxElement), 3); - QCOMPARE(titleBoxElement.firstChild().isNull(), false); - QCOMPARE(titleBoxElement.lastChild().isNull(), false); - QCOMPARE(titleBoxElement.previousSibling().isNull(), false); - QCOMPARE(titleBoxElement.previousSibling().isText(), true); - QCOMPARE(titleBoxElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(titleBoxElement.nextSibling().isNull(), false); - QCOMPARE(titleBoxElement.nextSibling().isText(), true); - QCOMPARE(titleBoxElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(titleBoxElement.localName(), QString("text-box")); - - // for the title text-box - KoXmlElement titleParElement; - titleParElement = titleBoxElement.firstChildElement(); - QCOMPARE(titleParElement.isNull(), false); - QCOMPARE(titleParElement.isElement(), true); - QCOMPARE(titleParElement.parentNode().isNull(), false); - QCOMPARE(titleParElement.parentNode() == titleBoxElement, true); - QCOMPARE(KoXml::childNodesCount(titleParElement), 1); - QCOMPARE(titleParElement.firstChild().isNull(), false); - QCOMPARE(titleParElement.lastChild().isNull(), false); - QCOMPARE(titleParElement.previousSibling().isNull(), false); - QCOMPARE(titleParElement.previousSibling().isText(), true); - QCOMPARE(titleParElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(titleParElement.nextSibling().isNull(), false); - QCOMPARE(titleParElement.nextSibling().isText(), true); - QCOMPARE(titleParElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(titleParElement.localName(), QString("p")); - QCOMPARE(titleParElement.attributeNS(textNS, "style-name", ""), QString("P1")); - QCOMPARE(titleParElement.text(), QString("Foobar")); - - whiteSpace = titleFrameElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // for the subtitle frame - KoXmlElement subtitleFrameElement; - subtitleFrameElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(subtitleFrameElement.isNull(), false); - QCOMPARE(subtitleFrameElement.isElement(), true); - QCOMPARE(subtitleFrameElement.parentNode().isNull(), false); - QCOMPARE(subtitleFrameElement.parentNode() == titlePageElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleFrameElement), 3); - QCOMPARE(subtitleFrameElement.firstChild().isNull(), false); - QCOMPARE(subtitleFrameElement.lastChild().isNull(), false); - QCOMPARE(subtitleFrameElement.previousSibling().isNull(), false); - QCOMPARE(subtitleFrameElement.nextSibling().isNull(), false); - QCOMPARE(subtitleFrameElement.localName(), QString("frame")); - QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr2")); - QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "class", ""), QString("subtitle")); - QCOMPARE(subtitleFrameElement.hasAttributeNS(presentationNS, "user-transformed"), false); - QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P3")); - QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "height", ""), QString("13.231cm")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm")); - QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "y", ""), QString("5.838cm")); - - // of the subtitle frame - KoXmlElement subtitleBoxElement; - subtitleBoxElement = subtitleFrameElement.firstChildElement(); - QCOMPARE(subtitleBoxElement.isNull(), false); - QCOMPARE(subtitleBoxElement.isElement(), true); - QCOMPARE(subtitleBoxElement.parentNode().isNull(), false); - QCOMPARE(subtitleBoxElement.parentNode() == subtitleFrameElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleBoxElement), 3); - QCOMPARE(subtitleBoxElement.firstChild().isNull(), false); - QCOMPARE(subtitleBoxElement.lastChild().isNull(), false); - QCOMPARE(subtitleBoxElement.previousSibling().isNull(), false); - QCOMPARE(subtitleBoxElement.previousSibling().isText(), true); - QCOMPARE(subtitleBoxElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(subtitleBoxElement.nextSibling().isNull(), false); - QCOMPARE(subtitleBoxElement.nextSibling().isText(), true); - QCOMPARE(subtitleBoxElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(subtitleBoxElement.localName(), QString("text-box")); - - // for the subtitle text-box - KoXmlElement subtitleParElement; - subtitleParElement = subtitleBoxElement.firstChildElement(); - QCOMPARE(subtitleParElement.isNull(), false); - QCOMPARE(subtitleParElement.isElement(), true); - QCOMPARE(subtitleParElement.parentNode().isNull(), false); - QCOMPARE(subtitleParElement.parentNode() == subtitleBoxElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleParElement), 1); - QCOMPARE(subtitleParElement.firstChild().isNull(), false); - QCOMPARE(subtitleParElement.lastChild().isNull(), false); - QCOMPARE(subtitleParElement.previousSibling().isNull(), false); - QCOMPARE(subtitleParElement.previousSibling().isText(), true); - QCOMPARE(subtitleParElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(subtitleParElement.nextSibling().isNull(), false); - QCOMPARE(subtitleParElement.nextSibling().isText(), true); - QCOMPARE(subtitleParElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(subtitleParElement.localName(), QString("p")); - QCOMPARE(subtitleParElement.attributeNS(textNS, "style-name", ""), QString("P3")); - QCOMPARE(subtitleParElement.text(), QString("Foo")); -} - -void TestXmlReader::testSimpleOpenDocumentFormula() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml from a simple OpenDocument formula - // this is essentially MathML - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << " "; - xmlstream << " "; - xmlstream << " E"; - xmlstream << " ="; - xmlstream << " "; - xmlstream << " mc"; - xmlstream << " 2"; - xmlstream << " "; - xmlstream << " "; - xmlstream << " E = mc^2 "; - xmlstream << " "; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - const char* mathNS = "http://www.w3.org/1998/Math/MathML"; - - // - KoXmlElement mathElement; - mathElement = doc.documentElement(); - QCOMPARE(mathElement.isNull(), false); - QCOMPARE(mathElement.isElement(), true); - QCOMPARE(mathElement.parentNode().isNull(), false); - QCOMPARE(mathElement.parentNode().toDocument() == doc, true); - QCOMPARE(mathElement.firstChild().isNull(), false); - QCOMPARE(mathElement.lastChild().isNull(), false); - QCOMPARE(mathElement.previousSibling().isNull(), false); - QCOMPARE(mathElement.nextSibling().isNull(), true); - QCOMPARE(mathElement.localName(), QString("math")); - - // - KoXmlElement semanticsElement; - semanticsElement = KoXml::namedItemNS(mathElement, mathNS, "semantics"); - QCOMPARE(semanticsElement.isNull(), false); - QCOMPARE(semanticsElement.isElement(), true); - QCOMPARE(semanticsElement.parentNode().isNull(), false); - QCOMPARE(semanticsElement.parentNode().toElement() == mathElement, true); - QCOMPARE(semanticsElement.firstChild().isNull(), false); - QCOMPARE(semanticsElement.lastChild().isNull(), false); - QCOMPARE(semanticsElement.previousSibling().isNull(), false); - QCOMPARE(semanticsElement.previousSibling().isText(), true); - QCOMPARE(semanticsElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(semanticsElement.nextSibling().isNull(), true); - QCOMPARE(semanticsElement.localName(), QString("semantics")); - - // the same but without namedItemNS - KoXmlElement semantics2Element; - semantics2Element = mathElement.firstChildElement(); - QCOMPARE(semantics2Element.isNull(), false); - QCOMPARE(semantics2Element.isElement(), true); - QCOMPARE(semantics2Element.parentNode().isNull(), false); - QCOMPARE(semantics2Element.parentNode().toElement() == mathElement, true); - QCOMPARE(semantics2Element.firstChild().isNull(), false); - QCOMPARE(semantics2Element.lastChild().isNull(), false); - QCOMPARE(semantics2Element.previousSibling().isNull(), false); - QCOMPARE(semantics2Element.previousSibling().isText(), true); - QCOMPARE(semantics2Element.previousSibling().previousSibling().isNull(), true); - QCOMPARE(semantics2Element.nextSibling().isNull(), true); - QCOMPARE(semantics2Element.localName(), QString("semantics")); - - // - KoXmlElement mrowElement; - mrowElement = semanticsElement.firstChildElement(); - QCOMPARE(mrowElement.isNull(), false); - QCOMPARE(mrowElement.isElement(), true); - QCOMPARE(mrowElement.parentNode().isNull(), false); - QCOMPARE(mrowElement.parentNode().toElement() == semanticsElement, true); - QCOMPARE(mrowElement.firstChild().isNull(), false); - QCOMPARE(mrowElement.lastChild().isNull(), false); - QCOMPARE(mrowElement.previousSibling().isNull(), false); - QCOMPARE(mrowElement.previousSibling().isText(), true); - QCOMPARE(mrowElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(mrowElement.nextSibling().isNull(), false); - QCOMPARE(mrowElement.localName(), QString("mrow")); - - // for "E" - KoXmlElement miElement; - miElement = mrowElement.firstChildElement(); - QCOMPARE(miElement.isNull(), false); - QCOMPARE(miElement.isElement(), true); - QCOMPARE(miElement.parentNode().isNull(), false); - QCOMPARE(miElement.parentNode().toElement() == mrowElement, true); - QCOMPARE(miElement.firstChild().isNull(), false); - QCOMPARE(miElement.lastChild().isNull(), false); - QCOMPARE(miElement.previousSibling().isNull(), false); - QCOMPARE(miElement.previousSibling().isText(), true); - QCOMPARE(miElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(miElement.nextSibling().isNull(), false); - QCOMPARE(miElement.localName(), QString("mi")); - - KoXmlNode whiteSpace = miElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // for "=" - KoXmlElement moElement; - moElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(moElement.isNull(), false); - QCOMPARE(moElement.isElement(), true); - QCOMPARE(moElement.parentNode().isNull(), false); - QCOMPARE(moElement.parentNode().toElement() == mrowElement, true); - QCOMPARE(moElement.firstChild().isNull(), false); - QCOMPARE(moElement.lastChild().isNull(), false); - QCOMPARE(moElement.previousSibling().isNull(), false); - QCOMPARE(moElement.nextSibling().isNull(), false); - QCOMPARE(moElement.localName(), QString("mo")); - QCOMPARE(moElement.attributeNS(mathNS, "stretchy", ""), QString("false")); - - whiteSpace = moElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // for "mc" and superscripted "2" - KoXmlElement msupElement; - msupElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(msupElement.isNull(), false); - QCOMPARE(msupElement.isElement(), true); - QCOMPARE(msupElement.parentNode().isNull(), false); - QCOMPARE(msupElement.parentNode().toElement() == mrowElement, true); - QCOMPARE(msupElement.firstChild().isNull(), false); - QCOMPARE(msupElement.lastChild().isNull(), false); - QCOMPARE(msupElement.previousSibling().isNull(), false); - QCOMPARE(msupElement.previousSibling().isText(), true); - QCOMPARE(msupElement.previousSibling().previousSibling().isNull(), false); - QCOMPARE(msupElement.nextSibling().isNull(), false); - QCOMPARE(msupElement.nextSibling().isText(), true); - QCOMPARE(msupElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(msupElement.localName(), QString("msup")); - - // inside the for "mc" - KoXmlElement mcElement; - mcElement = msupElement.firstChildElement(); - QCOMPARE(mcElement.isNull(), false); - QCOMPARE(mcElement.isElement(), true); - QCOMPARE(mcElement.parentNode().isNull(), false); - QCOMPARE(mcElement.parentNode().toElement() == msupElement, true); - QCOMPARE(mcElement.firstChild().isNull(), false); - QCOMPARE(mcElement.lastChild().isNull(), false); - QCOMPARE(mcElement.previousSibling().isNull(), false); - QCOMPARE(mcElement.previousSibling().isText(), true); - QCOMPARE(mcElement.previousSibling().previousSibling().isNull(), true); - QCOMPARE(mcElement.nextSibling().isNull(), false); - QCOMPARE(mcElement.localName(), QString("mi")); - QCOMPARE(mcElement.text(), QString("mc")); - QCOMPARE(mcElement.attributeNS(mathNS, "fontstyle", ""), QString("italic")); - - // inside the for "2" (superscript) - whiteSpace = mcElement.nextSibling(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - KoXmlElement mnElement; - mnElement = whiteSpace.nextSibling().toElement(); - QCOMPARE(mnElement.isNull(), false); - QCOMPARE(mnElement.isElement(), true); - QCOMPARE(mnElement.parentNode().isNull(), false); - QCOMPARE(mnElement.parentNode().toElement() == msupElement, true); - QCOMPARE(mnElement.firstChild().isNull(), false); - QCOMPARE(mnElement.lastChild().isNull(), false); - QCOMPARE(mnElement.previousSibling().isNull(), false); - QCOMPARE(mnElement.nextSibling().isNull(), false); - QCOMPARE(mnElement.nextSibling().isText(), true); - QCOMPARE(mnElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(mnElement.localName(), QString("mn")); - QCOMPARE(mnElement.text(), QString("2")); - - whiteSpace = semanticsElement.lastChild(); - QCOMPARE(whiteSpace.isNull(), false); - QCOMPARE(whiteSpace.isText(), true); - - // - KoXmlElement annotationElement; - annotationElement = whiteSpace.previousSibling().toElement(); - QCOMPARE(annotationElement.isNull(), false); - QCOMPARE(annotationElement.isElement(), true); - QCOMPARE(annotationElement.parentNode().isNull(), false); - QCOMPARE(annotationElement.parentNode().toElement() == semanticsElement, true); - QCOMPARE(annotationElement.firstChild().isNull(), false); - QCOMPARE(annotationElement.lastChild().isNull(), false); - QCOMPARE(annotationElement.previousSibling().isNull(), false); - QCOMPARE(annotationElement.nextSibling().isNull(), false); - QCOMPARE(annotationElement.nextSibling().isText(), true); - QCOMPARE(annotationElement.nextSibling().nextSibling().isNull(), true); - QCOMPARE(annotationElement.localName(), QString("annotation")); - QCOMPARE(annotationElement.text(), QString("E = mc^2 ")); - QCOMPARE(annotationElement.attributeNS(mathNS, "encoding", ""), QString("StarMath 5.0")); -} - -void TestXmlReader::testLargeOpenDocumentSpreadsheet() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - int sheetCount = 4; - int rowCount = 200; - int colCount = 200 / 16; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // content.xml - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << "\n"; - for (int i = 0; i < sheetCount; i++) { - QString sheetName = QString("Sheet%1").arg(i + 1); - xmlstream << "\n"; - for (int j = 0; j < rowCount; j++) { - xmlstream << "\n"; - for (int k = 0; k < colCount; k++) { - xmlstream << ""; - xmlstream << "Hello, world"; - xmlstream << "\n"; - } - xmlstream << "\n"; - } - xmlstream << "\n"; - } - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << "\n"; - xmldevice.close(); - - printf("Raw XML size: %lld KB\n", xmldevice.size() / 1024); - - - QTime timer; - KoXmlDocument doc; - - timer.start(); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - if (!errorMsg.isEmpty()) { - qDebug("Error: %s", qPrintable(errorMsg)); - return; - } - - printf("Large spreadsheet: KoXmlDocument parsing time is %d ms\n", timer.elapsed()); - - // release memory taken by the XML document content - //xmlstream.setDevice( 0 ); - - // namespaces that will be used - QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; - QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - - // - KoXmlElement bodyElement; - bodyElement = contentElement.firstChildElement(); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChildElement(); - QCOMPARE(spreadsheetElement.isNull(), false); - QCOMPARE(spreadsheetElement.isElement(), true); - QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); - - // now we visit every sheet, every row, every cell - timer.start(); - KoXmlElement tableElement; - tableElement = spreadsheetElement.firstChildElement(); - for (int table = 0; table < sheetCount; table++) { - QString tableName = QString("Sheet%1").arg(table + 1); - QCOMPARE(tableElement.isNull(), false); - QCOMPARE(tableElement.isElement(), true); - QCOMPARE(tableElement.localName(), QString("table")); - QCOMPARE(tableElement.hasAttributeNS(tableNS, "name"), true); - QCOMPARE(tableElement.attributeNS(tableNS, "name", ""), tableName); - QCOMPARE(tableElement.attributeNS(tableNS, "print", ""), QString("false")); - - // load everything for this table - //KoXml::load( tableElement, 99 ); - - QCOMPARE(tableElement.parentNode().isNull(), false); - QCOMPARE(tableElement.parentNode() == spreadsheetElement, true); - QCOMPARE(tableElement.firstChild().isNull(), false); - QCOMPARE(tableElement.lastChild().isNull(), false); - - KoXmlElement rowElement; - rowElement = tableElement.firstChildElement(); - for (int row = 0; row < rowCount; row++) { - QCOMPARE(rowElement.isNull(), false); - QCOMPARE(rowElement.isElement(), true); - QCOMPARE(rowElement.localName(), QString("table-row")); - QCOMPARE(rowElement.parentNode().isNull(), false); - QCOMPARE(rowElement.parentNode() == tableElement, true); - QCOMPARE(rowElement.firstChild().isNull(), false); - QCOMPARE(rowElement.lastChild().isNull(), false); - - KoXmlElement cellElement; - cellElement = rowElement.firstChildElement(); - for (int col = 0; col < colCount; col++) { - QCOMPARE(cellElement.isNull(), false); - QCOMPARE(cellElement.isElement(), true); - QCOMPARE(cellElement.localName(), QString("table-cell")); - QCOMPARE(cellElement.text(), QString("Hello, world")); - QCOMPARE(cellElement.hasAttributeNS(officeNS, "value-type"), true); - QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string")); - QCOMPARE(cellElement.parentNode().isNull(), false); - QCOMPARE(cellElement.parentNode() == rowElement, true); - QCOMPARE(cellElement.firstChild().isNull(), false); - QCOMPARE(cellElement.lastChild().isNull(), false); - cellElement = cellElement.nextSibling().nextSibling().toElement(); - } - - //KoXml::unload( rowElement ); - rowElement = rowElement.nextSibling().nextSibling().toElement(); - } - - KoXml::unload(tableElement); - tableElement = tableElement.nextSibling().nextSibling().toElement(); - } - - printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed()); -} - -void TestXmlReader::testExternalOpenDocumentSpreadsheet(const QString& filename) -{ - QProcess unzip; - QStringList arguments; - arguments << "-o" << filename << "content.xml"; - - printf("Unzipping content.xml from %s...\n", qPrintable(filename)); - - unzip.start("unzip", arguments); - if (!unzip.waitForStarted()) { - printf("Error: can't invoke unzip. Check your PATH and installation!\n\n"); - return; - } - - if (!unzip.waitForFinished()) { - printf("Error: unzip failed, can't continue!\n\n"); - return; - } - - printf("Processing content.xml....\n"); - - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QFile xmlfile("content.xml"); - if (!xmlfile.open(QFile::ReadOnly)) { - printf("Can not open file '%s'\n", qPrintable(filename)); - return; - } - - printf("Test external file: %s %lld KB\n", qPrintable(filename), xmlfile.size() / 1024); - - QTime timer; - timer.start(); - - KoXmlDocument doc; - - QCOMPARE(KoXml::setDocument(doc, &xmlfile, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - printf("External spreadsheet: parsing time is %d ms\n", timer.elapsed()); - - // namespaces that will be used - QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; - QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; - QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; - - // - KoXmlElement contentElement; - contentElement = doc.documentElement(); - QCOMPARE(contentElement.isNull(), false); - QCOMPARE(contentElement.isElement(), true); - QCOMPARE(contentElement.localName(), QString("document-content")); - - long totalCellCount = 0; - - KoXmlElement bodyElement; - forEachElement(bodyElement, contentElement) { - // - if (bodyElement.localName() != QString("body")) - continue; - - // now we iterate inside the body - timer.start(); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChild().toElement(); - QCOMPARE(spreadsheetElement.isNull(), false); - QCOMPARE(spreadsheetElement.isElement(), true); - QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); - - // now we visit every sheet - long tableCount = -1; - KoXmlElement tableElement; - tableElement = spreadsheetElement.firstChild().toElement(); - for (;;) { - if (tableElement.isNull()) - break; - - if (tableElement.localName() != QString("table")) { - tableElement = tableElement.nextSibling().toElement(); - continue; - } - - QString tableName = tableElement.attributeNS(tableNS, "name", ""); - tableCount++; - - printf(" sheet #%ld (%s): ", tableCount + 1, qPrintable(tableName)); - - // use to preload everything in this sheet, will slow it down! - // KoXml::load( tableElement, 50 ); - - long rowCount = -1; - long cellCount = -1; - - KoXmlElement rowElement; - rowElement = tableElement.firstChild().toElement(); - for (;;) { - if (rowElement.isNull()) - break; - - if (rowElement.localName() != QString("table-row")) { - rowElement = rowElement.nextSibling().toElement(); - continue; - } - - rowCount++; - KoXml::load(rowElement, 4); - - QCOMPARE(rowElement.isElement(), true); - QCOMPARE(rowElement.localName(), QString("table-row")); - QCOMPARE(rowElement.parentNode().isNull(), false); - QCOMPARE(rowElement.parentNode() == tableElement, true); - - KoXmlElement cellElement; - cellElement = rowElement.firstChild().toElement(); - for (; ;) { - if (cellElement.isNull()) - break; - - if (cellElement.localName() != QString("table-cell")) { - cellElement = cellElement.nextSibling().toElement(); - continue; - } - - cellCount++; - - QCOMPARE(cellElement.isNull(), false); - QCOMPARE(cellElement.isElement(), true); - QCOMPARE(cellElement.localName(), QString("table-cell")); - QString text1 = cellElement.text(); - QString text2 = cellElement.text(); - QCOMPARE(text1, text2); - QString type1 = cellElement.attributeNS(officeNS, "value-type", QString()); - QString type2 = cellElement.attributeNS(officeNS, "value-type", QString()); - QCOMPARE(type1, type2); - QString style1 = cellElement.attributeNS(tableNS, "style-name", QString()); - QString style2 = cellElement.attributeNS(tableNS, "style-name", QString()); - QCOMPARE(style1, style2); - - QCOMPARE(cellElement.parentNode().isNull(), false); - QCOMPARE(cellElement.parentNode() == rowElement, true); - - cellElement = cellElement.nextSibling().toElement(); - } - - - // better not to unload, freeing memory takes time - KoXml::unload(rowElement); - - rowElement = rowElement.nextSibling().toElement(); - } - - printf(" %ld rows, %ld cells\n", rowCount + 1, cellCount + 1); - totalCellCount += (cellCount + 1); - - // IMPORTANT: helps minimizing memory usage !! - // we do not need that element anymore, so just throw it away - KoXml::unload(tableElement); - - tableElement = tableElement.nextSibling().toElement(); - } - - KoXml::unload(spreadsheetElement); - } - - printf("Total number of cells: %ld\n", totalCellCount); - - int elapsed = timer.elapsed(); - printf("External spreadsheet: iterating time is %d ms\n", elapsed); - if (elapsed > 0) - printf(" approx. %ld cells/second\n", totalCellCount*1000 / elapsed); - - // uncomment to check the XML - xmlfile.remove(); -} - -QTEST_GUILESS_MAIN(TestXmlReader) -#include - diff --git a/libs/odf/tests/TestXmlWriter.cpp b/libs/odf/tests/TestXmlWriter.cpp index 80f1d6a324..579238e5ee 100644 --- a/libs/odf/tests/TestXmlWriter.cpp +++ b/libs/odf/tests/TestXmlWriter.cpp @@ -1,254 +1,253 @@ /* This file is part of the KDE project * Copyright (C) 2004 David Faure * Copyright 2008 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include class TestXmlWriter : public QObject { Q_OBJECT private Q_SLOTS: void testDocytype(); void testEmtpyElement(); void testAttributes(); void testIndent(); void testTextNode(); void testTextSpan(); void testTextSpanWithTabCache(); void testProcessingInstruction(); void testAddManifestEntry(); void testEscapingLongString(); void testEscalingLongString2(); void testConfig(); void speedTest(); private: void setup(const char *publicId = 0, const char *systemId = 0); QString content(); KoXmlWriter *writer; QBuffer *buffer; }; void TestXmlWriter::setup(const char *publicId, const char *systemId) { buffer = new QBuffer(); buffer->open( QIODevice::WriteOnly ); writer = new KoXmlWriter( buffer ); writer->startDocument( "dummy", publicId, systemId ); writer->startElement( "dummy" ); } QString TestXmlWriter::content() { writer->endElement(); writer->endDocument(); buffer->putChar( '\0' ); /*null-terminate*/ buffer->close(); QString stringContent = QString::fromUtf8(buffer->data()); int index = stringContent.indexOf("', index); stringContent = stringContent.mid(index+1, stringContent.length() - index - 11).trimmed(); return stringContent; } void TestXmlWriter::testDocytype() { setup("foo", "bar"); QCOMPARE(content(), QString()); QString stringContent = QString::fromUtf8(buffer->data()); QCOMPARE(stringContent, QString("\n" "\n\n")); } void TestXmlWriter::testAttributes() { setup(); writer->startElement("test"); writer->addAttribute("a", "val"); writer->addAttribute("b", "<\">"); writer->addAttribute("c", -42); writer->addAttribute("d", 1234.56789012345); - writer->addAttributePt("e", 1234.56789012345); writer->addAttribute("f", false); writer->addAttribute("g", true); writer->endElement(); - QCOMPARE(content(), QString("")); + QCOMPARE(content(), QString("")); } void TestXmlWriter::testEmtpyElement() { setup(); writer->startElement("m"); writer->endElement(); QCOMPARE(content(), QString("")); } void TestXmlWriter::testIndent() { setup(); writer->startElement("a"); writer->startElement("b"); writer->startElement("c"); writer->endElement(); writer->endElement(); writer->endElement(); QCOMPARE(content(), QString("\n \n \n \n ")); } void TestXmlWriter::testTextNode() { setup(); writer->startElement("a"); writer->startElement("b", false /*no indent*/); writer->startElement("c"); writer->endElement(); writer->addTextNode("te"); writer->addTextNode("xt"); writer->endElement(); writer->endElement(); QCOMPARE(content(), QString("\n text\n ")); } void TestXmlWriter::testTextSpan() { setup(); writer->startElement("p", false /*no indent*/); writer->addTextSpan(QString::fromLatin1(" \t\n foo ")); writer->endElement(); QCOMPARE(content(), QString("

foo

")); } void TestXmlWriter::testTextSpanWithTabCache() { setup(); writer->startElement("p", false /*no indent*/); QMap tabCache; tabCache.insert(3, 0); writer->addTextSpan(QString::fromUtf8(" \t\n foö "), tabCache); writer->endElement(); QCOMPARE(content(), QString::fromUtf8("

" " foö

")); } void TestXmlWriter::testProcessingInstruction() { setup(); writer->startElement("p", false /*no indent*/); writer->addProcessingInstruction("opendocument foobar"); writer->addTextSpan(QString::fromLatin1("foo")); writer->endElement(); QCOMPARE(content(), QString("

foo

")); } void TestXmlWriter::testAddManifestEntry() { setup(); writer->addManifestEntry(QString::fromLatin1("foo/bar/blah"), QString::fromLatin1("mime/type")); QCOMPARE(content(), QString("")); } void TestXmlWriter::testEscapingLongString() { int sz = 15000; // must be more than KoXmlWriter::s_escapeBufferLen QString x(sz); x.fill('x', sz); x += '&'; setup(); writer->startElement("test"); writer->addAttribute("a", x); writer->endElement(); QString expected = ""; QCOMPARE(content(), QString(expected)); } void TestXmlWriter::testEscalingLongString2() { QString longPath; for (uint i = 0 ; i < 1000 ; ++i) longPath += QString::fromLatin1("M10 10L20 20 "); setup(); writer->startElement("test"); writer->addAttribute("a", longPath); writer->endElement(); QString expected = ""; QCOMPARE(content(), expected); } void TestXmlWriter::testConfig() { setup(); const bool val = true; const int num = 1; const qreal numdouble = 5.0; writer->addConfigItem(QString::fromLatin1("TestConfigBool"), val); writer->addConfigItem(QString::fromLatin1("TestConfigInt"), num); writer->addConfigItem(QString::fromLatin1("TestConfigDouble"), numdouble); QCOMPARE(content(), QString("true\n" " 1\n" " 5")); } static const int NumParagraphs = 30000; void TestXmlWriter::speedTest() { QTime time; time.start(); QString paragText = QString::fromUtf8("This is the text of the paragraph. I'm including a euro sign to test encoding issues: €"); QString styleName = "Heading 1"; QFile out(QString::fromLatin1("out5.xml")); if (out.open(QIODevice::WriteOnly)) { KoXmlWriter writer(&out); writer.startDocument("rootelem"); writer.startElement("rootelem"); for (int i = 0 ; i < NumParagraphs ; ++i) { writer.startElement("paragraph"); writer.addAttribute("text:style-name", styleName); writer.addTextNode(paragText); writer.endElement(); } writer.endElement(); writer.endDocument(); } out.close(); out.remove(); qDebug("writing %i XML elements using KoXmlWriter: %i ms", NumParagraphs, time.elapsed()); // TODO we might want to convert this into a QBenchmark test } QTEST_GUILESS_MAIN(TestXmlWriter) #include diff --git a/libs/pigment/KoColorConversionSystem.cpp b/libs/pigment/KoColorConversionSystem.cpp index b1c0e06380..c95210ad1b 100644 --- a/libs/pigment/KoColorConversionSystem.cpp +++ b/libs/pigment/KoColorConversionSystem.cpp @@ -1,505 +1,507 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; 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 "KoColorConversionSystem.h" #include "KoColorConversionSystem_p.h" #include #include #include "KoColorConversionAlphaTransformation.h" #include "KoColorConversionTransformation.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoCopyColorConversionTransformation.h" #include "KoMultipleColorConversionTransformation.h" KoColorConversionSystem::KoColorConversionSystem(RegistryInterface *registryInterface) : d(new Private(registryInterface)) { } KoColorConversionSystem::~KoColorConversionSystem() { qDeleteAll(d->graph); qDeleteAll(d->vertexes); delete d; } void KoColorConversionSystem::connectToEngine(Node* _node, Node* _engine) { Vertex* v1 = createVertex(_node, _engine); Vertex* v2 = createVertex(_engine, _node); v1->conserveColorInformation = !_node->isGray; v2->conserveColorInformation = !_node->isGray; v1->conserveDynamicRange = _engine->isHdr; v2->conserveDynamicRange = _engine->isHdr; } KoColorConversionSystem::Node* KoColorConversionSystem::insertEngine(const KoColorSpaceEngine* engine) { NodeKey key(engine->id(), engine->id(), engine->id()); Node* n = new Node; n->modelId = engine->id(); n->depthId = engine->id(); n->profileName = engine->id(); n->referenceDepth = 64; // engine don't have reference depth, d->graph.insert(key, n); n->init(engine); return n; } void KoColorConversionSystem::insertColorSpace(const KoColorSpaceFactory* csf) { dbgPigment << "Inserting color space " << csf->name() << " (" << csf->id() << ") Model: " << csf->colorModelId() << " Depth: " << csf->colorDepthId() << " into the CCS"; const QList profiles = d->registryInterface->profilesFor(csf); QString modelId = csf->colorModelId().id(); QString depthId = csf->colorDepthId().id(); if (profiles.isEmpty()) { // There is no profile for this CS, create a node without profile name if the color engine isn't icc-based if (csf->colorSpaceEngine() != "icc") { Node* n = nodeFor(modelId, depthId, "default"); n->init(csf); } else { dbgPigment << "Cannot add node for " << csf->name() << ", since there are no profiles available"; } } else { // Initialise the nodes Q_FOREACH (const KoColorProfile* profile, profiles) { Node* n = nodeFor(modelId, depthId, profile->name()); n->init(csf); if (!csf->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(csf->colorSpaceEngine()); Q_ASSERT(engine); NodeKey engineKey(engine->id(), engine->id(), engine->id()); Node* engineNode = 0; QHash::ConstIterator it = d->graph.constFind(engineKey); if (it != d->graph.constEnd()) { engineNode = it.value(); } else { engineNode = insertEngine(engine); } connectToEngine(n, engineNode); } } } // Construct a link for "custom" transformation const QList cctfs = csf->colorConversionLinks(); Q_FOREACH (KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } void KoColorConversionSystem::insertColorProfile(const KoColorProfile* _profile) { dbgPigmentCCS << _profile->name(); const QList< const KoColorSpaceFactory* >& factories = d->registryInterface->colorSpacesFor(_profile); Q_FOREACH (const KoColorSpaceFactory* factory, factories) { QString modelId = factory->colorModelId().id(); QString depthId = factory->colorDepthId().id(); Node* n = nodeFor(modelId, depthId, _profile->name()); n->init(factory); if (!factory->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(factory->colorSpaceEngine()); Q_ASSERT(engine); Node* engineNode = d->graph[ NodeKey(engine->id(), engine->id(), engine->id())]; Q_ASSERT(engineNode); connectToEngine(n, engineNode); } const QList cctfs = factory->colorConversionLinks(); Q_FOREACH (KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); if (srcNode == n || dstNode == n) { // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } } } const KoColorSpace* KoColorConversionSystem::defaultColorSpaceForNode(const Node* node) const { return d->registryInterface->colorSpace(node->modelId, node->depthId, node->profileName); } KoColorConversionSystem::Node* KoColorConversionSystem::createNode(const QString& _modelId, const QString& _depthId, const QString& _profileName) { Node* n = new Node; n->modelId = _modelId; n->depthId = _depthId; n->profileName = _profileName; d->graph.insert(NodeKey(_modelId, _depthId, _profileName), n); return n; } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorSpace* _colorSpace) const { const KoColorProfile* profile = _colorSpace->profile(); return nodeFor(_colorSpace->colorModelId().id(), _colorSpace->colorDepthId().id(), profile ? profile->name() : "default"); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) const { dbgPigmentCCS << "Look for node: " << _colorModelId << " " << _colorDepthId << " " << _profileName; return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const NodeKey& key) const { dbgPigmentCCS << "Look for node: " << key.modelId << " " << key.depthId << " " << key.profileName << " " << d->graph.value(key); return d->graph.value(key); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) { return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorConversionSystem::NodeKey& key) { QHash::ConstIterator it = d->graph.constFind(key); if (it != d->graph.constEnd()) { return it.value(); } else { return createNode(key.modelId, key.depthId, key.profileName); } } QList KoColorConversionSystem::nodesFor(const QString& _modelId, const QString& _depthId) { QList nodes; Q_FOREACH (Node* node, d->graph) { if (node->modelId == _modelId && node->depthId == _depthId) { nodes << node; } } return nodes; } KoColorConversionTransformation* KoColorConversionSystem::createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*srcColorSpace == *dstColorSpace) { return new KoCopyColorConversionTransformation(srcColorSpace); } Q_ASSERT(srcColorSpace); Q_ASSERT(dstColorSpace); dbgPigmentCCS << srcColorSpace->id() << (srcColorSpace->profile() ? srcColorSpace->profile()->name() : "default"); dbgPigmentCCS << dstColorSpace->id() << (dstColorSpace->profile() ? dstColorSpace->profile()->name() : "default"); Path path = findBestPath( - nodeFor(srcColorSpace), - nodeFor(dstColorSpace)); + nodeFor(srcColorSpace), + nodeFor(dstColorSpace)); Q_ASSERT(path.length() > 0); KoColorConversionTransformation* transfo = createTransformationFromPath(path, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); Q_ASSERT(*transfo->srcColorSpace() == *srcColorSpace); Q_ASSERT(*transfo->dstColorSpace() == *dstColorSpace); Q_ASSERT(transfo); return transfo; } void KoColorConversionSystem::createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const { // TODO This function currently only select the best conversion only based on the transformation // from colorSpace to one of the color spaces in the list, but not the other way around // it might be worth to look also the return path. const Node* csNode = nodeFor(colorSpace); PathQualityChecker pQC(csNode->referenceDepth, !csNode->isHdr, !csNode->isGray); // Look for a color conversion Path bestPath; typedef QPair KoID2KoID; Q_FOREACH (const KoID2KoID & possibility, possibilities) { const KoColorSpaceFactory* csf = d->registryInterface->colorSpaceFactory(possibility.first.id(), possibility.second.id()); if (csf) { Path path = findBestPath(csNode, nodeFor(csf->colorModelId().id(), csf->colorDepthId().id(), csf->defaultProfile())); Q_ASSERT(path.length() > 0); path.isGood = pQC.isGoodPath(path); if (bestPath.isEmpty()) { bestPath = path; } else if ((!bestPath.isGood && path.isGood) || pQC.lessWorseThan(path, bestPath)) { bestPath = path; } } } Q_ASSERT(!bestPath.isEmpty()); const KoColorSpace* endColorSpace = defaultColorSpaceForNode(bestPath.endNode()); fromCS = createTransformationFromPath(bestPath, colorSpace, endColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Path returnPath = findBestPath(bestPath.endNode(), csNode); Q_ASSERT(!returnPath.isEmpty()); toCS = createTransformationFromPath(returnPath, endColorSpace, colorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Q_ASSERT(*toCS->dstColorSpace() == *fromCS->srcColorSpace()); Q_ASSERT(*fromCS->dstColorSpace() == *toCS->srcColorSpace()); } KoColorConversionTransformation* KoColorConversionSystem::createTransformationFromPath(const Path &path, const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(srcColorSpace->colorModelId().id() == path.startNode()->modelId); Q_ASSERT(srcColorSpace->colorDepthId().id() == path.startNode()->depthId); Q_ASSERT(dstColorSpace->colorModelId().id() == path.endNode()->modelId); Q_ASSERT(dstColorSpace->colorDepthId().id() == path.endNode()->depthId); KoColorConversionTransformation* transfo; const QList< Path::node2factory > pathOfNode = path.compressedPath(); if (pathOfNode.size() == 2) { // Direct connection transfo = pathOfNode[1].second->createColorTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { KoMultipleColorConversionTransformation* mccTransfo = new KoMultipleColorConversionTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); transfo = mccTransfo; // Get the first intermediary color space dbgPigmentCCS << pathOfNode[ 0 ].first->id() << " to " << pathOfNode[ 1 ].first->id(); const KoColorSpace* intermCS = - defaultColorSpaceForNode(pathOfNode[1].first); + defaultColorSpaceForNode(pathOfNode[1].first); mccTransfo->appendTransfo(pathOfNode[1].second->createColorTransformation(srcColorSpace, intermCS, renderingIntent, conversionFlags)); for (int i = 2; i < pathOfNode.size() - 1; i++) { dbgPigmentCCS << pathOfNode[ i - 1 ].first->id() << " to " << pathOfNode[ i ].first->id(); const KoColorSpace* intermCS2 = defaultColorSpaceForNode(pathOfNode[i].first); Q_ASSERT(intermCS2); mccTransfo->appendTransfo(pathOfNode[i].second->createColorTransformation(intermCS, intermCS2, renderingIntent, conversionFlags)); intermCS = intermCS2; } dbgPigmentCCS << pathOfNode[ pathOfNode.size() - 2 ].first->id() << " to " << pathOfNode[ pathOfNode.size() - 1 ].first->id(); mccTransfo->appendTransfo(pathOfNode.last().second->createColorTransformation(intermCS, dstColorSpace, renderingIntent, conversionFlags)); } return transfo; } KoColorConversionSystem::Vertex* KoColorConversionSystem::vertexBetween(KoColorConversionSystem::Node* srcNode, KoColorConversionSystem::Node* dstNode) { Q_FOREACH (Vertex* oV, srcNode->outputVertexes) { if (oV->dstNode == dstNode) { return oV; } } return 0; } KoColorConversionSystem::Vertex* KoColorConversionSystem::createVertex(Node* srcNode, Node* dstNode) { Vertex* v = new Vertex(srcNode, dstNode); srcNode->outputVertexes.append(v); d->vertexes.append(v); return v; } // -- Graph visualization functions -- QString KoColorConversionSystem::vertexToDot(KoColorConversionSystem::Vertex* v, const QString &options) const { return QString(" \"%1\" -> \"%2\" %3\n").arg(v->srcNode->id()).arg(v->dstNode->id()).arg(options); } QString KoColorConversionSystem::toDot() const { QString dot = "digraph CCS {\n"; Q_FOREACH (Vertex* oV, d->vertexes) { dot += vertexToDot(oV, "default") ; } dot += "}\n"; return dot; } bool KoColorConversionSystem::existsPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { dbgPigmentCCS << "srcModelId = " << srcModelId << " srcDepthId = " << srcDepthId << " srcProfileName = " << srcProfileName << " dstModelId = " << dstModelId << " dstDepthId = " << dstDepthId << " dstProfileName = " << dstProfileName; const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path path = findBestPath(srcNode, dstNode); bool exist = !path.isEmpty(); return exist; } bool KoColorConversionSystem::existsGoodPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path path = findBestPath(srcNode, dstNode); bool existAndGood = path.isGood; return existAndGood; } QString KoColorConversionSystem::bestPathToDot(const QString& srcKey, const QString& dstKey) const { const Node* srcNode = 0; const Node* dstNode = 0; Q_FOREACH (Node* node, d->graph) { if (node->id() == srcKey) { srcNode = node; } if (node->id() == dstKey) { dstNode = node; } } Path p = findBestPath(srcNode, dstNode); Q_ASSERT(!p.isEmpty()); QString dot = "digraph CCS {\n" + - QString(" \"%1\" [color=red]\n").arg(srcNode->id()) + - QString(" \"%1\" [color=red]\n").arg(dstNode->id()); + QString(" \"%1\" [color=red]\n").arg(srcNode->id()) + + QString(" \"%1\" [color=red]\n").arg(dstNode->id()); Q_FOREACH (Vertex* oV, d->vertexes) { QString options; if (p.vertexes.contains(oV)) { options = "[color=red]"; } dot += vertexToDot(oV, options) ; } dot += "}\n"; return dot; } inline KoColorConversionSystem::Path KoColorConversionSystem::findBestPathImpl2(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr, bool ignoreColorCorrectness) const { PathQualityChecker pQC(qMin(srcNode->referenceDepth, dstNode->referenceDepth), ignoreHdr, ignoreColorCorrectness); Node2PathHash node2path; // current best path to reach a given node QList possiblePaths; // list of all paths // Generate the initial list of paths Q_FOREACH (Vertex* v, srcNode->outputVertexes) { if (v->dstNode->isInitialized) { Path p; p.appendVertex(v); Node* endNode = v->dstNode; if (endNode == dstNode) { Q_ASSERT(pQC.isGoodPath(p)); // <- it's a direct link, it has to be a good path p.isGood = true; return p; } else { Q_ASSERT(!node2path.contains(endNode)); // That would be a total fuck up if there are two vertices between two nodes node2path.insert(endNode, p); possiblePaths.append(p); } } } Path currentBestPath; // Continue while there are any possibilities remaining while (possiblePaths.size() > 0) { // Loop through all paths and explore one step further const QList currentPaths = possiblePaths; for (const Path &p : currentPaths) { const Node* endNode = p.endNode(); for (Vertex* v : endNode->outputVertexes) { if (v->dstNode->isInitialized && !p.contains(v->dstNode)) { Path newP = p; // Candidate newP.appendVertex(v); Node* newEndNode = v->dstNode; if (newEndNode == dstNode) { if (pQC.isGoodPath(newP)) { // Victory newP.isGood = true; return newP; } else if (pQC.lessWorseThan(newP, currentBestPath)) { - Q_ASSERT(newP.startNode()->id() == currentBestPath.startNode()->id()); - Q_ASSERT(newP.endNode()->id() == currentBestPath.endNode()->id()); - // Can we do better than dumping memory values??? - // warnPigment << pQC.lessWorseThan(newP, currentBestPath) << " " << newP << " " << currentBestPath; - currentBestPath = newP; + if (newP.startNode() && newP.endNode() && currentBestPath.startNode() && currentBestPath.endNode()) { + Q_ASSERT(newP.startNode()->id() == currentBestPath.startNode()->id()); + Q_ASSERT(newP.endNode()->id() == currentBestPath.endNode()->id()); + // Can we do better than dumping memory values??? + // warnPigment << pQC.lessWorseThan(newP, currentBestPath) << " " << newP << " " << currentBestPath; + currentBestPath = newP; + } } } else { // This is an incomplete path. Check if there's a better way to get to its endpoint. Node2PathHash::Iterator it = node2path.find(newEndNode); if (it != node2path.end()) { Path &p2 = it.value(); if (pQC.lessWorseThan(newP, p2)) { p2 = newP; possiblePaths.append(newP); } } else { node2path.insert(newEndNode, newP); possiblePaths.append(newP); } } } } possiblePaths.removeAll(p); // Remove from list of remaining paths } } if (!currentBestPath.isEmpty()) { warnPigment << "No good path from " << srcNode->id() << " to " << dstNode->id() << " found : length = " << currentBestPath.length() << " cost = " << currentBestPath.cost << " referenceDepth = " << currentBestPath.referenceDepth << " respectColorCorrectness = " << currentBestPath.respectColorCorrectness << " isGood = " << currentBestPath.isGood ; return currentBestPath; } errorPigment << "No path from " << srcNode->id() << " to " << dstNode->id() << " found not "; return currentBestPath; } inline KoColorConversionSystem::Path KoColorConversionSystem::findBestPathImpl(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); return findBestPathImpl2(srcNode, dstNode, ignoreHdr, (srcNode->isGray || dstNode->isGray)); } KoColorConversionSystem::Path KoColorConversionSystem::findBestPath(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); dbgPigmentCCS << "Find best path between " << srcNode->id() << " and " << dstNode->id(); if (srcNode->isHdr && dstNode->isHdr) { return findBestPathImpl(srcNode, dstNode, false); } else { return findBestPathImpl(srcNode, dstNode, true); } } diff --git a/libs/pigment/KoColorConversionSystem_p.h b/libs/pigment/KoColorConversionSystem_p.h index 5d21f930a7..ac3a0d26f1 100644 --- a/libs/pigment/KoColorConversionSystem_p.h +++ b/libs/pigment/KoColorConversionSystem_p.h @@ -1,327 +1,335 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; 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 KOCOLORCONVERSIONSYSTEM_P_H #define KOCOLORCONVERSIONSYSTEM_P_H #include "DebugPigment.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" #include "KoColorConversionTransformationFactory.h" #include "KoColorSpaceEngine.h" #include struct KoColorConversionSystem::Node { Node() : isHdr(false) , isInitialized(false) , referenceDepth(0) , isGray(false) , crossingCost(1) , colorSpaceFactory(0) , isEngine(false) , engine(0) {} void init(const KoColorSpaceFactory* _colorSpaceFactory) { dbgPigment << "Initialise " << modelId << " " << depthId << " " << profileName; if (isInitialized) { dbgPigment << "Re-initializing node. Old factory" << colorSpaceFactory << "new factory" << _colorSpaceFactory; } isInitialized = true; if (_colorSpaceFactory) { isHdr = _colorSpaceFactory->isHdr(); colorSpaceFactory = _colorSpaceFactory; referenceDepth = _colorSpaceFactory->referenceDepth(); isGray = (_colorSpaceFactory->colorModelId() == GrayAColorModelID || _colorSpaceFactory->colorModelId() == GrayColorModelID || _colorSpaceFactory->colorModelId() == AlphaColorModelID); } } void init(const KoColorSpaceEngine* _engine) { Q_ASSERT(!isInitialized); isEngine = true; isInitialized = true; isHdr = true; engine = _engine; } QString id() const { return modelId + " " + depthId + " " + profileName; } QString modelId; QString depthId; QString profileName; bool isHdr; bool isInitialized; int referenceDepth; QList outputVertexes; bool isGray; int crossingCost; const KoColorSpaceFactory* colorSpaceFactory; bool isEngine; const KoColorSpaceEngine* engine; }; Q_DECLARE_TYPEINFO(KoColorConversionSystem::Node, Q_MOVABLE_TYPE); struct KoColorConversionSystem::Vertex { Vertex(Node* _srcNode, Node* _dstNode) : srcNode(_srcNode) , dstNode(_dstNode) , factoryFromSrc(0) , factoryFromDst(0) { } ~Vertex() { if (factoryFromSrc == factoryFromDst) { delete factoryFromSrc; } else { delete factoryFromSrc; delete factoryFromDst; } } void setFactoryFromSrc(KoColorConversionTransformationFactory* factory) { factoryFromSrc = factory; initParameter(factoryFromSrc); } void setFactoryFromDst(KoColorConversionTransformationFactory* factory) { factoryFromDst = factory; if (!factoryFromSrc) initParameter(factoryFromDst); } void initParameter(KoColorConversionTransformationFactory* transfo) { conserveColorInformation = transfo->conserveColorInformation(); conserveDynamicRange = transfo->conserveDynamicRange(); } KoColorConversionTransformationFactory* factory() { if (factoryFromSrc) return factoryFromSrc; return factoryFromDst; } Node* srcNode; Node* dstNode; bool conserveColorInformation; bool conserveDynamicRange; private: KoColorConversionTransformationFactory* factoryFromSrc; // Factory provided by the destination node KoColorConversionTransformationFactory* factoryFromDst; // Factory provided by the destination node }; struct KoColorConversionSystem::NodeKey { NodeKey(const QString &_modelId, const QString &_depthId, const QString &_profileName) : modelId(_modelId) , depthId(_depthId) , profileName(_profileName) {} bool operator==(const KoColorConversionSystem::NodeKey& rhs) const { return modelId == rhs.modelId && depthId == rhs.depthId && profileName == rhs.profileName; } QString modelId; QString depthId; QString profileName; }; Q_DECLARE_TYPEINFO(KoColorConversionSystem::NodeKey, Q_MOVABLE_TYPE); struct KoColorConversionSystem::Path { Path() : respectColorCorrectness(true) , referenceDepth(0) , keepDynamicRange(true) , isGood(false) , cost(0) {} Node* startNode() { - return (vertexes.first())->srcNode; + return vertexes.size() > 0 ? + (vertexes.first())->srcNode + : 0; } bool operator==(const Path &other) const { - return other.vertexes == vertexes; + return other.vertexes == vertexes; } const Node* startNode() const { - return (vertexes.first())->srcNode; + return vertexes.size() > 0 ? + (vertexes.first())->srcNode + : 0; } Node* endNode() { - return (vertexes.last())->dstNode; + return vertexes.size() > 0 ? + (vertexes.last())->dstNode + : 0; } const Node* endNode() const { - return (vertexes.last())->dstNode; + return vertexes.size() > 0 ? + (vertexes.last())->dstNode + : 0; } bool isEmpty() const { return vertexes.isEmpty(); } void appendVertex(Vertex* v) { if (vertexes.empty()) { referenceDepth = v->srcNode->referenceDepth; } vertexes.append(v); if (!v->conserveColorInformation) respectColorCorrectness = false; if (!v->conserveDynamicRange) keepDynamicRange = false; referenceDepth = qMin(referenceDepth, v->dstNode->referenceDepth); cost += v->dstNode->crossingCost; } // Compress path to hide the Engine node and correctly select the factory typedef QPair node2factory; QList< node2factory > compressedPath() const { QList< node2factory > nodes; nodes.push_back(node2factory(vertexes.first()->srcNode , vertexes.first()->factory())); const KoColorConversionTransformationAbstractFactory* previousFactory = 0; Q_FOREACH (Vertex* vertex, vertexes) { // Unless the node is the icc node, add it to the path Node* n = vertex->dstNode; if (n->isEngine) { previousFactory = n->engine; } else { nodes.push_back( node2factory(n, previousFactory ? previousFactory : vertex->factory())); previousFactory = 0; } } return nodes; } int length() const { return vertexes.size(); } bool contains(Node* n) const { Q_FOREACH (Vertex* v, vertexes) { if (v->srcNode == n || v->dstNode == n) { return true; } } return false; } QList vertexes; bool respectColorCorrectness; int referenceDepth; bool keepDynamicRange; bool isGood; int cost; }; Q_DECLARE_TYPEINFO(KoColorConversionSystem::Path, Q_MOVABLE_TYPE); inline QDebug operator<<(QDebug dbg, const KoColorConversionSystem::Path &path) { bool havePrintedFirst = false; Q_FOREACH (const KoColorConversionSystem::Vertex *v, path.vertexes) { if (!havePrintedFirst) { dbg.nospace() << v->srcNode->id(); havePrintedFirst = true; } dbg.nospace() << "->" << v->dstNode->id(); } return dbg.space(); } typedef QHash Node2PathHash; uint qHash(const KoColorConversionSystem::NodeKey &key) { return qHash(key.modelId) + qHash(key.depthId); } struct Q_DECL_HIDDEN KoColorConversionSystem::Private { Private(RegistryInterface *_registryInterface) : registryInterface(_registryInterface) {} QHash graph; QList vertexes; RegistryInterface *registryInterface; }; #define CHECK_ONE_AND_NOT_THE_OTHER(name) \ if(path1. name && !path2. name) \ { \ return true; \ } \ if(!path1. name && path2. name) \ { \ return false; \ } struct PathQualityChecker { PathQualityChecker(int _referenceDepth, bool _ignoreHdr, bool _ignoreColorCorrectness) : referenceDepth(_referenceDepth) , ignoreHdr(_ignoreHdr) , ignoreColorCorrectness(_ignoreColorCorrectness) {} /// @return true if the path maximize all the criterions (except length) inline bool isGoodPath(const KoColorConversionSystem::Path & path) const { return (path.respectColorCorrectness || ignoreColorCorrectness) && (path.referenceDepth >= referenceDepth) && (path.keepDynamicRange || ignoreHdr); } /** * Compare two paths. */ inline bool lessWorseThan(const KoColorConversionSystem::Path &path1, const KoColorConversionSystem::Path &path2) const { // There is no point in comparing two paths which doesn't start from the same node or doesn't end at the same node if (!ignoreHdr) { CHECK_ONE_AND_NOT_THE_OTHER(keepDynamicRange) } if (!ignoreColorCorrectness) { CHECK_ONE_AND_NOT_THE_OTHER(respectColorCorrectness) } if (path1.referenceDepth == path2.referenceDepth) { return path1.cost < path2.cost; // if they have the same cost, well anyway you have to choose one, and there is no point in keeping one and not the other } return path1.referenceDepth > path2.referenceDepth; } int referenceDepth; bool ignoreHdr; bool ignoreColorCorrectness; }; #undef CHECK_ONE_AND_NOT_THE_OTHER #endif diff --git a/libs/pigment/KoColorSpaceRegistry.h b/libs/pigment/KoColorSpaceRegistry.h index 8db1fe2054..3bee4e3a26 100644 --- a/libs/pigment/KoColorSpaceRegistry.h +++ b/libs/pigment/KoColorSpaceRegistry.h @@ -1,360 +1,361 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004,2010 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACEREGISTRY_H #define KOCOLORSPACEREGISTRY_H #include #include #include #include "kritapigment_export.h" #include #include #include class KoColorProfile; class KoColorConversionSystem; class KoColorConversionCache; class KoColorConversionTransformation; /** * The registry for colorspaces and profiles. * This class contains: * - a registry of colorspace instantiated with specific profiles. * - a registry of singleton colorspace factories. * - a registry of icc profiles * * Locking policy details: * * Basically, we have two levels of locks in the registry: * 1) (outer level) is Private::registrylock, which controls the structures * of the color space registry itself * 2) (inner level) is KoColorProfileStorage::Private::lock controls * the structures related to profiles. * * The locks can be taken individually, but if you are going to take both * of them, you should always follow the order 1) registry; 2) profiles. * Otherwise you'll get a deadlock. * * To avoid recursive deadlocks, all the dependent classes * (KoColorConversionSystem and KoColorSpaceFactory) now do not use the direct * links to the registry. Instead, they use special private interfaces that * skip recursive locking and ensure we take a lock twice. */ class KRITAPIGMENT_EXPORT KoColorSpaceRegistry { public: KoColorSpaceRegistry(); enum ColorSpaceListVisibility { OnlyUserVisible = 1, ///< Only user visible color space AllColorSpaces = 4 ///< All color space even those not visible to the user }; enum ColorSpaceListProfilesSelection { OnlyDefaultProfile = 1, ///< Only add the default profile AllProfiles = 4 ///< Add all profiles }; /** * Return an instance of the KoColorSpaceRegistry * Creates an instance if that has never happened before and returns the singleton instance. */ static KoColorSpaceRegistry * instance(); virtual ~KoColorSpaceRegistry(); public: /** * add a color space to the registry * @param item the color space factory to add */ void add(KoColorSpaceFactory* item); /** * Remove a color space factory from the registry. Note that it is the * responsibility of the caller to ensure that the colorspaces are not * used anymore. */ void remove(KoColorSpaceFactory* item); /** * Add a profile to the profile map but do not add it to the * color conversion system yet. * @param profile the new profile to be registered. */ void addProfileToMap(KoColorProfile *p); /** * register the profile with the color space registry * @param profile the new profile to be registered so it can be combined with * colorspaces. */ void addProfile(KoColorProfile* profile); void addProfile(const KoColorProfile* profile); // TODO why ? void removeProfile(KoColorProfile* profile); /** * Create an alias to a profile with a different name. Then @ref profileByName * will return the profile @p to when passed @p name as a parameter. */ void addProfileAlias(const QString& name, const QString& to); /** * @return the profile alias, or name if not aliased */ QString profileAlias(const QString& name) const; /** * create a profile of the specified type. */ const KoColorProfile *createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData); /** * Return a profile by its given name, or 0 if none registered. * @return a profile by its given name, or 0 if none registered. * @param name the product name as set on the profile. * @see addProfile() * @see KoColorProfile::productName() */ const KoColorProfile * profileByName(const QString & name) const ; /** * Returns a profile by its unique id stored/calculated in the header. * The first call to this function might take long, because the map is * created on the first use only (atm used by SVG only) * @param id unique ProfileID of the profile (MD5 sum of its header) * @return the profile or 0 if not found */ const KoColorProfile *profileByUniqueId(const QByteArray &id) const; bool profileIsCompatible(const KoColorProfile* profile, const QString &colorSpaceId); /** * Return the list of profiles for a colorspace with the argument id. * Profiles will not work with any color space, you can query which profiles * that are registered with this registry can be used in combination with the * argument factory. * @param colorSpaceId the colorspace-id with which all the returned profiles will work. * @return a list of profiles for the factory */ QList profilesFor(const QString& csID) const; QString defaultProfileForColorSpace(const QString &colorSpaceId) const; /** * This function is called by the color space to create a color conversion * between two color space. This function search in the graph of transformations * the best possible path between the two color space. */ KoColorConversionTransformation* createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * This function creates two transformations, one from the color space and one to the * color space. The destination color space is picked from a list of color space, such * as the conversion between the two color space is of the best quality. * * The typical use case of this function is for KoColorTransformationFactory which * doesn't support all color spaces, so unsupported color space have to find an * acceptable conversion in order to use that KoColorTransformationFactory. * * @param colorSpace the source color space * @param possibilities a list of color space among which we need to find the best * conversion * @param fromCS the conversion from the source color space will be affected to this * variable * @param toCS the revert conversion to the source color space will be affected to this * variable */ void createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const; /** * Return a colorspace that works with the parameter profile. * @param colorSpaceId the ID string of the colorspace that you want to have returned * @param profile the profile be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile); /** * Return a colorspace that works with the parameter profile. * @param profileName the name of the KoColorProfile to be combined with the colorspace * @return the wanted colorspace, or 0 when the cs and profile can not be combined. */ const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName); const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId); /** * Return the id of the colorspace that have the defined colorModelId with colorDepthId. * @param colorModelId id of the color model * @param colorDepthId id of the color depth * @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids */ QString colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const; /** * It's a convenient function that behave like the above. * Return the id of the colorspace that have the defined colorModelId with colorDepthId. * @param colorModelId id of the color model * @param colorDepthId id of the color depth * @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids */ QString colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const; /** * @return a the identifiant of the color model for the given color space id. * * This function is a compatibility function used to get the color space from * all kra files. */ KoID colorSpaceColorModelId(const QString & _colorSpaceId) const; /** * @return a the identifiant of the color depth for the given color space id. * * This function is a compatibility function used to get the color space from * all kra files. */ KoID colorSpaceColorDepthId(const QString & _colorSpaceId) const; /** * Convenience methods to get the often used alpha colorspaces */ const KoColorSpace *alpha8(); const KoColorSpace *alpha16(); #include #ifdef HAVE_OPENEXR const KoColorSpace *alpha16f(); #endif const KoColorSpace *alpha32f(); /** * Convenience method to get an RGBA 8bit colorspace. If a profile is not specified, * an sRGB profile will be used. * @param profileName the name of an RGB color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb8(const QString &profileName = QString()); /** * Convenience method to get an RGBA 8bit colorspace with the given profile. * @param profile an RGB profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb8(const KoColorProfile * profile); /** * Convenience method to get an RGBA 16bit colorspace. If a profile is not specified, * an sRGB profile will be used. * @param profileName the name of an RGB color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb16(const QString &profileName = QString()); /** * Convenience method to get an RGBA 16bit colorspace with the given profile. * @param profile an RGB profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * rgb16(const KoColorProfile * profile); /** * Convenience method to get an Lab 16bit colorspace. If a profile is not specified, * an Lab profile with a D50 whitepoint will be used. * @param profileName the name of an Lab color profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * lab16(const QString &profileName = QString()); /** * Convenience method to get an Lab 16bit colorspace with the given profile. * @param profile an Lab profile * @return the wanted colorspace, or 0 if the color space and profile can not be combined. */ const KoColorSpace * lab16(const KoColorProfile * profile); /** * @return the list of available color models */ QList colorModelsList(ColorSpaceListVisibility option) const; /** * @return the list of available color models for the given colorModelId */ QList colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const; /** * @return the list of available color models for the given colorModelId */ QList colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const; /** * @return the cache of color conversion transformation to be use by KoColorSpace */ KoColorConversionCache* colorConversionCache() const; /** * @return a permanent colorspace owned by the registry, of the same type and profile * as the one given in argument */ const KoColorSpace* permanentColorspace(const KoColorSpace* _colorSpace); /** * This function return a list of all the keys in KoID format by using the name() method * on the objects stored in the registry. */ QList listKeys() const; private: friend class KisCsConversionTest; friend class KisIteratorTest; + friend class KisIteratorNGTest; friend class KisPainterTest; friend class KisCrashFilterTest; friend class KoColorSpacesBenchmark; friend class TestKoColorSpaceSanity; friend class TestColorConversionSystem; friend class FriendOfColorSpaceRegistry; /** * @return a list with an instance of all color space with their default profile. */ QList allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection); /** * @return the color conversion system use by the registry and the color * spaces to create color conversion transformation. * * WARNING: conversion system is guared by the registry locks, don't * use it anywhere other than unttests! */ const KoColorConversionSystem* colorConversionSystem() const; private: KoColorSpaceRegistry(const KoColorSpaceRegistry&); KoColorSpaceRegistry operator=(const KoColorSpaceRegistry&); void init(); private: struct Private; Private * const d; }; #endif // KOCOLORSPACEREGISTRY_H diff --git a/libs/pigment/colorspaces/KoAlphaColorSpace.cpp b/libs/pigment/colorspaces/KoAlphaColorSpace.cpp index 41df03a41b..4ff9f7475b 100644 --- a/libs/pigment/colorspaces/KoAlphaColorSpace.cpp +++ b/libs/pigment/colorspaces/KoAlphaColorSpace.cpp @@ -1,280 +1,304 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoAlphaColorSpace.h" #include #include #include #include #include #include "KoChannelInfo.h" #include "KoID.h" #include "KoIntegerMaths.h" #include "KoCompositeOpOver.h" #include "KoCompositeOpErase.h" #include "KoCompositeOpCopy2.h" #include "KoCompositeOpAlphaDarken.h" #include "KoCompositeOpBase.h" #include namespace { template KoChannelInfo::enumChannelValueType channelInfoIdFromChannelType(); template <> inline KoChannelInfo::enumChannelValueType channelInfoIdFromChannelType() { return KoChannelInfo::UINT8; } template <> inline KoChannelInfo::enumChannelValueType channelInfoIdFromChannelType() { return KoChannelInfo::UINT16; } #ifdef HAVE_OPENEXR template <> inline KoChannelInfo::enumChannelValueType channelInfoIdFromChannelType() { return KoChannelInfo::FLOAT16; } #endif template <> inline KoChannelInfo::enumChannelValueType channelInfoIdFromChannelType() { return KoChannelInfo::FLOAT32; } } template class AlphaColorSpaceMultiplyOp : public KoCompositeOpBase< Traits, AlphaColorSpaceMultiplyOp> { typedef KoCompositeOpBase> base_class; typedef typename Traits::channels_type channels_type; public: AlphaColorSpaceMultiplyOp(const KoColorSpace* cs) : base_class(cs, COMPOSITE_MULT, i18n("Multiply"), KoCompositeOp::categoryArithmetic()) { } public: template inline static channels_type composeColorChannels(const channels_type* src, channels_type srcAlpha, channels_type* dst, channels_type dstAlpha, channels_type maskAlpha, channels_type opacity, const QBitArray& channelFlags) { using namespace Arithmetic; Q_UNUSED(allChannelFlags); Q_UNUSED(src); Q_UNUSED(dst); Q_UNUSED(channelFlags); if (!alphaLocked) { // use internal parallelism for multiplication! srcAlpha = mul(srcAlpha, maskAlpha); dstAlpha = mul(dstAlpha, opacity); dstAlpha = mul(srcAlpha, dstAlpha); } return dstAlpha; } }; template KoAlphaColorSpaceImpl<_CSTrait>::KoAlphaColorSpaceImpl() : KoColorSpaceAbstract<_CSTrait>(alphaIdFromChannelType().id(), alphaIdFromChannelType().name()) { this->addChannel(new KoChannelInfo(i18n("Alpha"), 0, 0, KoChannelInfo::ALPHA, channelInfoIdFromChannelType())); m_compositeOps << new KoCompositeOpOver<_CSTrait>(this) << new KoCompositeOpErase<_CSTrait>(this) << new KoCompositeOpCopy2<_CSTrait>(this) << new KoCompositeOpAlphaDarken<_CSTrait>(this) << new AlphaColorSpaceMultiplyOp<_CSTrait>(this); Q_FOREACH (KoCompositeOp *op, m_compositeOps) { this->addCompositeOp(op); } m_profile = new KoDummyColorProfile; } template KoAlphaColorSpaceImpl<_CSTrait>::~KoAlphaColorSpaceImpl() { qDeleteAll(m_compositeOps); delete m_profile; m_profile = 0; } template void KoAlphaColorSpaceImpl<_CSTrait>::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const { _CSTrait::nativeArray(dst)[0] = _MathsFromU8::scaleToA(c.alpha()); } template void KoAlphaColorSpaceImpl<_CSTrait>::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const { c->setRgba(qRgba(255, 255, 255, _MathsToU8::scaleToA(_CSTrait::nativeArray(src)[0]))); } template quint8 KoAlphaColorSpaceImpl<_CSTrait>::difference(const quint8 *src1, const quint8 *src2) const { return qAbs(_MathsToU8::scaleToA(_CSTrait::nativeArray(src2)[0] - _CSTrait::nativeArray(src1)[0])); } template quint8 KoAlphaColorSpaceImpl<_CSTrait>::differenceA(const quint8 *src1, const quint8 *src2) const { return difference(src1, src2); } template QString KoAlphaColorSpaceImpl<_CSTrait>::channelValueText(const quint8 *pixel, quint32 channelIndex) const { Q_ASSERT(channelIndex < this->channelCount()); const quint32 channelPosition = this->channels()[channelIndex]->pos(); return QString().setNum(_CSTrait::nativeArray(pixel)[channelPosition]); } template QString KoAlphaColorSpaceImpl<_CSTrait>::normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const { Q_ASSERT(channelIndex < this->channelCount()); const quint32 channelPosition = this->channels()[channelIndex]->pos(); return QString().setNum(KoColorSpaceMaths::scaleToA(_CSTrait::nativeArray(pixel)[channelPosition])); } template void KoAlphaColorSpaceImpl<_CSTrait>::convolveColors(quint8** colors, qreal * kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const { qreal totalAlpha = 0; while (nColors--) { qreal weight = *kernelValues; if (weight != 0) { totalAlpha += _CSTrait::nativeArray(*colors)[0] * weight; } ++colors; ++kernelValues; } if (channelFlags.isEmpty() || channelFlags.testBit(0)) { _CSTrait::nativeArray(dst)[0] = _Maths::clamp((totalAlpha / factor) + offset); } } template QImage KoAlphaColorSpaceImpl<_CSTrait>::convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * /*dstProfile*/, KoColorConversionTransformation::Intent /*renderingIntent*/, KoColorConversionTransformation::ConversionFlags /*conversionFlags*/) const { const channels_type *srcPtr = _CSTrait::nativeArray(data); QImage img(width, height, QImage::Format_Indexed8); QVector table; for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i)); img.setColorTable(table); quint8* data_img; for (int i = 0; i < height; ++i) { data_img = img.scanLine(i); for (int j = 0; j < width; ++j) { data_img[j] = _MathsToU8::scaleToA(*(srcPtr++)); } } return img; } template void KoAlphaColorSpaceImpl<_CSTrait>::toLabA16(const quint8 *src, quint8 *dst, quint32 nPixels) const { - quint16* lab = reinterpret_cast(dst); + const channels_type* srcPtr = _CSTrait::nativeArray(src); + quint16* dstPtr = reinterpret_cast(dst); + while (nPixels--) { - lab[3] = _CSTrait::nativeArray(src)[0]; - src++; - lab += 4; + dstPtr[0] = KoColorSpaceMaths::scaleToA(srcPtr[0]); + dstPtr[1] = UINT16_MAX / 2; + dstPtr[2] = UINT16_MAX / 2; + dstPtr[3] = UINT16_MAX; + + srcPtr++; + dstPtr += 4; } } template void KoAlphaColorSpaceImpl<_CSTrait>::fromLabA16(const quint8 *src, quint8 *dst, quint32 nPixels) const { - const quint16* lab = reinterpret_cast(src); + const quint16* srcPtr = reinterpret_cast(src); + channels_type* dstPtr = _CSTrait::nativeArray(dst); + while (nPixels--) { - _CSTrait::nativeArray(dst)[0] = lab[3]; - dst++; - lab += 4; + dstPtr[0] = KoColorSpaceMaths::scaleToA(UINT16_MULT(srcPtr[0], srcPtr[3])); + + dstPtr++; + srcPtr += 4; } } template void KoAlphaColorSpaceImpl<_CSTrait>::toRgbA16(const quint8 *src, quint8 *dst, quint32 nPixels) const { - quint16* rgb = reinterpret_cast(dst); + const channels_type* srcPtr = _CSTrait::nativeArray(src); + quint16* dstPtr = reinterpret_cast(dst); + while (nPixels--) { - rgb[3] = _CSTrait::nativeArray(src)[0]; - src++; - rgb += 4; + const quint16 gray = KoColorSpaceMaths::scaleToA(srcPtr[0]); + + dstPtr[0] = gray; + dstPtr[1] = gray; + dstPtr[2] = gray; + dstPtr[3] = UINT16_MAX; + + srcPtr++; + dstPtr += 4; } } template void KoAlphaColorSpaceImpl<_CSTrait>::fromRgbA16(const quint8 *src, quint8 *dst, quint32 nPixels) const { - const quint16* rgb = reinterpret_cast(src); + const quint16* srcPtr = reinterpret_cast(src); + channels_type* dstPtr = _CSTrait::nativeArray(dst); + while (nPixels--) { - _CSTrait::nativeArray(dst)[0] = rgb[3]; - dst++; - rgb += 4; + // WARNING: we consider red channel only! + dstPtr[0] = KoColorSpaceMaths::scaleToA(UINT16_MULT(srcPtr[0], srcPtr[3])); + + dstPtr++; + srcPtr += 4; } } template KoColorSpace* KoAlphaColorSpaceImpl<_CSTrait>::clone() const { return new KoAlphaColorSpaceImpl<_CSTrait>(); } template bool KoAlphaColorSpaceImpl<_CSTrait>::preferCompositionInSourceColorSpace() const { return true; } template class KoAlphaColorSpaceImpl; template class KoAlphaColorSpaceImpl; #ifdef HAVE_OPENEXR template class KoAlphaColorSpaceImpl; #endif template class KoAlphaColorSpaceImpl; /*********************************************************************************************/ /* KoAlphaColorSpaceFactoryImpl */ /*********************************************************************************************/ #include template QList KoAlphaColorSpaceFactoryImpl<_CSTrait>::colorConversionLinks() const { QList factories; + factories << new KoColorConversionFromAlphaTransformationFactoryImpl(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), "gray built-in"); + factories << new KoColorConversionToAlphaTransformationFactoryImpl(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), "gray built-in"); + factories << new KoColorConversionFromAlphaTransformationFactoryImpl(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), "default"); factories << new KoColorConversionToAlphaTransformationFactoryImpl(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), "default"); factories << new KoColorConversionFromAlphaTransformationFactoryImpl(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), "Lab identity built-in"); factories << new KoColorConversionToAlphaTransformationFactoryImpl(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), "Lab identity built-in"); return factories; } template class KoAlphaColorSpaceFactoryImpl; template class KoAlphaColorSpaceFactoryImpl; #ifdef HAVE_OPENEXR template class KoAlphaColorSpaceFactoryImpl; #endif template class KoAlphaColorSpaceFactoryImpl; diff --git a/libs/pigment/tests/TestColorConversionSystem.cpp b/libs/pigment/tests/TestColorConversionSystem.cpp index 80ee74547b..0bf1a75fae 100644 --- a/libs/pigment/tests/TestColorConversionSystem.cpp +++ b/libs/pigment/tests/TestColorConversionSystem.cpp @@ -1,237 +1,237 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestColorConversionSystem.h" #include #include #include #include #include #include TestColorConversionSystem::TestColorConversionSystem() { Q_FOREACH (const KoID& modelId, KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces)) { Q_FOREACH (const KoID& depthId, KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces)) { QList< const KoColorProfile * > profiles = KoColorSpaceRegistry::instance()->profilesFor( KoColorSpaceRegistry::instance()->colorSpaceId(modelId, depthId)); Q_FOREACH (const KoColorProfile * profile, profiles) { listModels.append(ModelDepthProfile(modelId.id(), depthId.id(), profile->name())); } } } //listModels.append(ModelDepthProfile(AlphaColorModelID.id(), Integer8BitsColorDepthID.id(), "")); } void TestColorConversionSystem::testConnections() { Q_FOREACH (const ModelDepthProfile& srcCS, listModels) { Q_FOREACH (const ModelDepthProfile& dstCS, listModels) { QVERIFY2(KoColorSpaceRegistry::instance()->colorConversionSystem()->existsPath(srcCS.model, srcCS.depth, srcCS.profile, dstCS.model, dstCS.depth, dstCS.profile) , QString("No path between %1 / %2 and %3 / %4").arg(srcCS.model).arg(srcCS.depth).arg(dstCS.model).arg(dstCS.depth).toLatin1()); } } } void TestColorConversionSystem::testGoodConnections() { int countFail = 0; Q_FOREACH (const ModelDepthProfile& srcCS, listModels) { Q_FOREACH (const ModelDepthProfile& dstCS, listModels) { if (!KoColorSpaceRegistry::instance()->colorConversionSystem()->existsGoodPath(srcCS.model, srcCS.depth, srcCS.profile , dstCS.model, dstCS.depth, dstCS.profile)) { ++countFail; dbgPigment << "No good path between \"" << srcCS.model << " " << srcCS.depth << " " << srcCS.profile << "\" \"" << dstCS.model << " " << dstCS.depth << " " << dstCS.profile << "\""; } } } int failed = 0; if (!KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0) && KoColorSpaceRegistry::instance()->colorSpace( "KS6", Float32BitsColorDepthID.id(), 0) ) { failed = 42; } QVERIFY2(countFail == failed, QString("%1 tests have fails (it should have been %2)").arg(countFail).arg(failed).toLatin1()); } #include void TestColorConversionSystem::testAlphaConversions() { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const KoColorSpace *rgb8 = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *rgb16 = KoColorSpaceRegistry::instance()->rgb16(); { KoColor c(QColor(255,255,255,255), alpha8); QCOMPARE(c.opacityU8(), quint8(255)); c.convertTo(rgb8); - QCOMPARE(c.toQColor(), QColor(254,255,255)); + QCOMPARE(c.toQColor(), QColor(255,255,255)); c.convertTo(alpha8); QCOMPARE(c.opacityU8(), quint8(255)); } { KoColor c(QColor(255,255,255,0), alpha8); c.convertTo(rgb8); - QCOMPARE(c.toQColor(), QColor(0,0,1,255)); + QCOMPARE(c.toQColor(), QColor(0,0,0,255)); c.convertTo(alpha8); QCOMPARE(c.opacityU8(), quint8(0)); } { KoColor c(QColor(255,255,255,128), alpha8); c.convertTo(rgb8); - QCOMPARE(c.toQColor(), QColor(118,120,120,255)); + QCOMPARE(c.toQColor(), QColor(129,129,129,255)); c.convertTo(alpha8); - QCOMPARE(c.opacityU8(), quint8(128)); + QCOMPARE(c.opacityU8(), quint8(138)); } { KoColor c(QColor(255,255,255,255), alpha8); QCOMPARE(c.opacityU8(), quint8(255)); c.convertTo(rgb16); - QCOMPARE(c.toQColor(), QColor(254,255,255)); + QCOMPARE(c.toQColor(), QColor(255,255,255)); c.convertTo(alpha8); QCOMPARE(c.opacityU8(), quint8(255)); } { KoColor c(QColor(255,255,255,0), alpha8); c.convertTo(rgb16); - QCOMPARE(c.toQColor(), QColor(0,0,1,255)); + QCOMPARE(c.toQColor(), QColor(0,0,0,255)); c.convertTo(alpha8); QCOMPARE(c.opacityU8(), quint8(0)); } { KoColor c(QColor(255,255,255,128), alpha8); c.convertTo(rgb16); - QCOMPARE(c.toQColor(), QColor(118,120,120,255)); + QCOMPARE(c.toQColor(), QColor(129,129,129,255)); c.convertTo(alpha8); - QCOMPARE(c.opacityU8(), quint8(128)); + QCOMPARE(c.opacityU8(), quint8(138)); } } void TestColorConversionSystem::testAlphaU16Conversions() { KoColorSpaceRegistry::instance(); const KoColorSpace *alpha16 = KoColorSpaceRegistry::instance()->alpha16(); const KoColorSpace *rgb8 = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *rgb16 = KoColorSpaceRegistry::instance()->rgb16(); { KoColor c(QColor(255,255,255,255), alpha16); QCOMPARE(c.opacityU8(), quint8(255)); c.convertTo(rgb8); - QCOMPARE(c.toQColor(), QColor(254,255,255)); + QCOMPARE(c.toQColor(), QColor(255,255,255)); c.convertTo(alpha16); QCOMPARE(c.opacityU8(), quint8(255)); } { KoColor c(QColor(255,255,255,0), alpha16); c.convertTo(rgb8); - QCOMPARE(c.toQColor(), QColor(0,0,1,255)); + QCOMPARE(c.toQColor(), QColor(0,0,0,255)); c.convertTo(alpha16); QCOMPARE(c.opacityU8(), quint8(0)); } { KoColor c(QColor(255,255,255,128), alpha16); c.convertTo(rgb8); - QCOMPARE(c.toQColor(), QColor(118,120,120,255)); + QCOMPARE(c.toQColor(), QColor(129,129,129,255)); c.convertTo(alpha16); - QCOMPARE(c.opacityU8(), quint8(128)); + QCOMPARE(c.opacityU8(), quint8(138)); } { KoColor c(QColor(255,255,255,255), alpha16); QCOMPARE(c.opacityU8(), quint8(255)); c.convertTo(rgb16); QCOMPARE(c.toQColor(), QColor(254,255,255)); c.convertTo(alpha16); QCOMPARE(c.opacityU8(), quint8(255)); } { KoColor c(QColor(255,255,255,0), alpha16); c.convertTo(rgb16); QCOMPARE(c.toQColor(), QColor(0,0,1,255)); c.convertTo(alpha16); QCOMPARE(c.opacityU8(), quint8(0)); } { KoColor c(QColor(255,255,255,128), alpha16); c.convertTo(rgb16); QCOMPARE(c.toQColor(), QColor(118,120,120,255)); c.convertTo(alpha16); QCOMPARE(c.opacityU8(), quint8(128)); } } void TestColorConversionSystem::benchmarkAlphaToRgbConversion() { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const KoColorSpace *rgb8 = KoColorSpaceRegistry::instance()->rgb8(); const int numPixels = 1024 * 4096; QByteArray srcBuf(numPixels * alpha8->pixelSize(), '\0'); QByteArray dstBuf(numPixels * rgb8->pixelSize(), '\0'); qsrand(1); for (int i = 0; i < srcBuf.size(); i++) { srcBuf[i] = qrand() & 0xFF; } QBENCHMARK { alpha8->convertPixelsTo((quint8*)srcBuf.data(), (quint8*)dstBuf.data(), rgb8, numPixels, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::Empty); } } void TestColorConversionSystem::benchmarkRgbToAlphaConversion() { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const KoColorSpace *rgb8 = KoColorSpaceRegistry::instance()->rgb8(); const int numPixels = 1024 * 4096; QByteArray srcBuf(numPixels * rgb8->pixelSize(), '\0'); QByteArray dstBuf(numPixels * alpha8->pixelSize(), '\0'); qsrand(1); for (int i = 0; i < srcBuf.size(); i++) { srcBuf[i] = qrand() & 0xFF; } QBENCHMARK { rgb8->convertPixelsTo((quint8*)srcBuf.data(), (quint8*)dstBuf.data(), alpha8, numPixels, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::Empty); } } QTEST_GUILESS_MAIN(TestColorConversionSystem) diff --git a/libs/pigment/tests/TestKoColor.cpp b/libs/pigment/tests/TestKoColor.cpp index b5d3cf45a0..2b30dc1daa 100644 --- a/libs/pigment/tests/TestKoColor.cpp +++ b/libs/pigment/tests/TestKoColor.cpp @@ -1,88 +1,90 @@ /* * Copyright (C) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestKoColor.h" #include #include #include "KoColorModelStandardIds.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "DebugPigment.h" +#include "sdk/tests/kistest.h" + bool nearEqualValue(int a, int b) { return qAbs(a - b) <= 1; } void TestKoColor::testForModel(QString model) { QColor qc(200, 125, 100); QList depthIDs = KoColorSpaceRegistry::instance()->colorDepthList(model, KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH (const KoID& depthId, depthIDs) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(model, depthId.id() , ""); if (cs) { KoColor kc(cs); kc.fromQColor(qc); QDomDocument doc; QDomElement elt = doc.createElement("color"); kc.toXML(doc, elt); doc.appendChild(elt); dbgPigment << doc.toString(); KoColor kcu = KoColor::fromXML(elt.firstChildElement(), depthId.id()); QVERIFY2(*(kc.colorSpace()) == *(kcu.colorSpace()), QString("Not identical color space (colorModelId = %1 depthId = %2) != (colorModelId = %3 depthId = %4) ") .arg(kc.colorSpace()->colorModelId().id()) .arg(kc.colorSpace()->colorDepthId().id()) .arg(kcu.colorSpace()->colorModelId().id()) .arg(kcu.colorSpace()->colorDepthId().id()).toLatin1()); QVERIFY(cs->difference(kcu.data(), kc.data()) <= 1); } } } void TestKoColor::testSerialization() { testForModel(RGBAColorModelID.id()); testForModel(XYZAColorModelID.id()); testForModel(LABAColorModelID.id()); testForModel(CMYKAColorModelID.id()); testForModel(GrayAColorModelID.id()); // we cannot test ycbcr since we cannot ship profiles //testForModel(YCbCrAColorModelID.id()); } void TestKoColor::testConversion() { QColor c = Qt::red; const KoColorSpace *csOrig = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *csDst = KoColorSpaceRegistry::instance()->lab16(); KoColor kc(csOrig); kc.fromQColor(c); kc.convertTo(csDst); } -QTEST_GUILESS_MAIN(TestKoColor) +KISTEST_MAIN(TestKoColor) diff --git a/libs/pigment/tests/TestKoColorSpaceSanity.cpp b/libs/pigment/tests/TestKoColorSpaceSanity.cpp index 9b9adc644c..67b5e09834 100644 --- a/libs/pigment/tests/TestKoColorSpaceSanity.cpp +++ b/libs/pigment/tests/TestKoColorSpaceSanity.cpp @@ -1,65 +1,66 @@ /* * Copyright (c) 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 "TestKoColorSpaceSanity.h" #include #include #include +#include "sdk/tests/kistest.h" void TestKoColorSpaceSanity::testChannelsInfo() { Q_FOREACH (const KoColorSpace* colorSpace, KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile)) { QCOMPARE(colorSpace->channelCount(), quint32(colorSpace->channels().size())); QList displayPositions; quint32 colorChannels = 0; quint32 size = 0; Q_FOREACH (KoChannelInfo* info, colorSpace->channels()) { if(info->channelType() == KoChannelInfo::COLOR ) { ++colorChannels; } // Check poses qint32 pos = info->pos(); QVERIFY(pos + info->size() <= (qint32)colorSpace->pixelSize()); Q_FOREACH (KoChannelInfo* info2, colorSpace->channels()) { if( info != info2 ) { QVERIFY( pos >= (info2->pos() + info2->size()) || pos + info->size() <= info2->pos()); } } // Check displayPosition quint32 displayPosition = info->displayPosition(); QVERIFY(displayPosition < colorSpace->channelCount()); QVERIFY(displayPositions.indexOf(displayPosition) == -1); displayPositions.push_back(displayPosition); size += info->size(); } QCOMPARE(size, colorSpace->pixelSize()); QCOMPARE(colorSpace->colorChannelCount(), colorChannels); } } -QTEST_GUILESS_MAIN(TestKoColorSpaceSanity) +KISTEST_MAIN(TestKoColorSpaceSanity) diff --git a/libs/store/KoXmlReaderForward.h b/libs/store/KoXmlReaderForward.h index a615d28cd0..87f22a12f1 100644 --- a/libs/store/KoXmlReaderForward.h +++ b/libs/store/KoXmlReaderForward.h @@ -1,37 +1,35 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat Copyright (C) 2007 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOXMLREADERFORWARD_H #define KOXMLREADERFORWARD_H // use standard QDom, useful to test KoXml classes against Qt's QDom -#define KOXML_USE_QDOM - #include typedef QDomNode KoXmlNode; typedef QDomElement KoXmlElement; typedef QDomText KoXmlText; typedef QDomCDATASection KoXmlCDATASection; typedef QDomDocumentType KoXmlDocumentType; typedef QDomDocument KoXmlDocument; #endif // KOXMLREADERFORWARD_H diff --git a/libs/store/KoXmlWriter.cpp b/libs/store/KoXmlWriter.cpp index 19e632903a..d429ac000e 100644 --- a/libs/store/KoXmlWriter.cpp +++ b/libs/store/KoXmlWriter.cpp @@ -1,569 +1,555 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoXmlWriter.h" #include #include #include #include #include "../global/kis_dom_utils.h" static const int s_indentBufferLength = 100; static const int s_escapeBufferLen = 10000; class Q_DECL_HIDDEN KoXmlWriter::Private { public: Private(QIODevice* dev_, int indentLevel = 0) : dev(dev_), baseIndentLevel(indentLevel) {} ~Private() { delete[] indentBuffer; delete[] escapeBuffer; //TODO: look at if we must delete "dev". For me we must delete it otherwise we will leak it } QIODevice* dev; QStack tags; int baseIndentLevel; char* indentBuffer; // maybe make it static, but then it needs a K_GLOBAL_STATIC // and would eat 1K all the time... Maybe refcount it :) char* escapeBuffer; // can't really be static if we want to be thread-safe }; KoXmlWriter::KoXmlWriter(QIODevice* dev, int indentLevel) : d(new Private(dev, indentLevel)) { init(); } void KoXmlWriter::init() { d->indentBuffer = new char[ s_indentBufferLength ]; memset(d->indentBuffer, ' ', s_indentBufferLength); *d->indentBuffer = '\n'; // write newline before indentation, in one go d->escapeBuffer = new char[s_escapeBufferLen]; if (!d->dev->isOpen()) d->dev->open(QIODevice::WriteOnly); } KoXmlWriter::~KoXmlWriter() { delete d; } void KoXmlWriter::startDocument(const char* rootElemName, const char* publicId, const char* systemId) { Q_ASSERT(d->tags.isEmpty()); writeCString("\n"); // There isn't much point in a doctype if there's no DTD to refer to // (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema) if (publicId) { writeCString("\n"); } } void KoXmlWriter::endDocument() { // just to do exactly like QDom does (newline at end of file). writeChar('\n'); Q_ASSERT(d->tags.isEmpty()); } // returns the value of indentInside of the parent bool KoXmlWriter::prepareForChild() { if (!d->tags.isEmpty()) { Tag& parent = d->tags.top(); if (!parent.hasChildren) { closeStartElement(parent); parent.hasChildren = true; parent.lastChildIsText = false; } if (parent.indentInside) { writeIndent(); } return parent.indentInside; } return true; } void KoXmlWriter::prepareForTextNode() { if (d->tags.isEmpty()) return; Tag& parent = d->tags.top(); if (!parent.hasChildren) { closeStartElement(parent); parent.hasChildren = true; parent.lastChildIsText = true; } } void KoXmlWriter::startElement(const char* tagName, bool indentInside) { Q_ASSERT(tagName != 0); // Tell parent that it has children bool parentIndent = prepareForChild(); d->tags.push(Tag(tagName, parentIndent && indentInside)); writeChar('<'); writeCString(tagName); //kDebug(s_area) << tagName; } void KoXmlWriter::addCompleteElement(const char* cstr) { prepareForChild(); writeCString(cstr); } void KoXmlWriter::addCompleteElement(QIODevice* indev) { prepareForChild(); const bool wasOpen = indev->isOpen(); // Always (re)open the device in readonly mode, it might be // already open but for writing, and we need to rewind. const bool openOk = indev->open(QIODevice::ReadOnly); Q_ASSERT(openOk); if (!openOk) { warnStore << "Failed to re-open the device! wasOpen=" << wasOpen; return; } QString indentString; indentString.fill((' '), indentLevel()); QByteArray indentBuf(indentString.toUtf8()); QByteArray buffer; while (!indev->atEnd()) { buffer = indev->readLine(); d->dev->write(indentBuf); d->dev->write(buffer); } if (!wasOpen) { // Restore initial state indev->close(); } } void KoXmlWriter::endElement() { if (d->tags.isEmpty()) warnStore << "EndElement() was called more times than startElement(). " "The generated XML will be invalid! " "Please report this bug (by saving the document to another format...)" << endl; Tag tag = d->tags.pop(); if (!tag.hasChildren) { writeCString("/>"); } else { if (tag.indentInside && !tag.lastChildIsText) { writeIndent(); } writeCString("'); } } void KoXmlWriter::addTextNode(const QByteArray& cstr) { // Same as the const char* version below, but here we know the size prepareForTextNode(); char* escaped = escapeForXML(cstr.constData(), cstr.size()); writeCString(escaped); if (escaped != d->escapeBuffer) delete[] escaped; } void KoXmlWriter::addTextNode(const char* cstr) { prepareForTextNode(); char* escaped = escapeForXML(cstr, -1); writeCString(escaped); if (escaped != d->escapeBuffer) delete[] escaped; } void KoXmlWriter::addProcessingInstruction(const char* cstr) { prepareForTextNode(); writeCString(""); } void KoXmlWriter::addAttribute(const char* attrName, const QByteArray& value) { // Same as the const char* one, but here we know the size writeChar(' '); writeCString(attrName); writeCString("=\""); char* escaped = escapeForXML(value.constData(), value.size()); writeCString(escaped); if (escaped != d->escapeBuffer) delete[] escaped; writeChar('"'); } void KoXmlWriter::addAttribute(const char* attrName, const char* value) { writeChar(' '); writeCString(attrName); writeCString("=\""); char* escaped = escapeForXML(value, -1); writeCString(escaped); if (escaped != d->escapeBuffer) delete[] escaped; writeChar('"'); } void KoXmlWriter::addAttribute(const char* attrName, double value) { addAttribute(attrName, KisDomUtils::toString(value)); } void KoXmlWriter::addAttribute(const char* attrName, float value) { addAttribute(attrName, KisDomUtils::toString(value)); } -void KoXmlWriter::addAttributePt(const char* attrName, double value) -{ - // WARNING: we don't write 'pt' into SVG anymore! We just use - // viewBox to align coordinates system with 'pt'. - addAttribute(attrName, KisDomUtils::toString(value)); -} - -void KoXmlWriter::addAttributePt(const char* attrName, float value) -{ - // WARNING: we don't write 'pt' into SVG anymore! We just use - // viewBox to align coordinates system with 'pt'. - addAttribute(attrName, KisDomUtils::toString(value)); -} - void KoXmlWriter::writeIndent() { // +1 because of the leading '\n' d->dev->write(d->indentBuffer, qMin(indentLevel() + 1, s_indentBufferLength)); } void KoXmlWriter::writeString(const QString& str) { // cachegrind says .utf8() is where most of the time is spent const QByteArray cstr = str.toUtf8(); d->dev->write(cstr); } // In case of a reallocation (ret value != d->buffer), the caller owns the return value, // it must delete it (with []) char* KoXmlWriter::escapeForXML(const char* source, int length = -1) const { // we're going to be pessimistic on char length; so lets make the outputLength less // the amount one char can take: 6 char* destBoundary = d->escapeBuffer + s_escapeBufferLen - 6; char* destination = d->escapeBuffer; char* output = d->escapeBuffer; const char* src = source; // src moves, source remains for (;;) { if (destination >= destBoundary) { // When we come to realize that our escaped string is going to // be bigger than the escape buffer (this shouldn't happen very often...), // we drop the idea of using it, and we allocate a bigger buffer. // Note that this if() can only be hit once per call to the method. if (length == -1) length = qstrlen(source); // expensive... uint newLength = length * 6 + 1; // worst case. 6 is due to " and ' char* buffer = new char[ newLength ]; destBoundary = buffer + newLength; uint amountOfCharsAlreadyCopied = destination - d->escapeBuffer; memcpy(buffer, d->escapeBuffer, amountOfCharsAlreadyCopied); output = buffer; destination = buffer + amountOfCharsAlreadyCopied; } switch (*src) { case 60: // < memcpy(destination, "<", 4); destination += 4; break; case 62: // > memcpy(destination, ">", 4); destination += 4; break; case 34: // " memcpy(destination, """, 6); destination += 6; break; #if 0 // needed? case 39: // ' memcpy(destination, "'", 6); destination += 6; break; #endif case 38: // & memcpy(destination, "&", 5); destination += 5; break; case 0: *destination = '\0'; return output; // Control codes accepted in XML 1.0 documents. case 9: case 10: case 13: *destination++ = *src++; continue; default: // Don't add control codes not accepted in XML 1.0 documents. if (*src > 0 && *src < 32) { ++src; } else { *destination++ = *src++; } continue; } ++src; } // NOTREACHED (see case 0) return output; } void KoXmlWriter::addManifestEntry(const QString& fullPath, const QString& mediaType) { startElement("manifest:file-entry"); addAttribute("manifest:media-type", mediaType); addAttribute("manifest:full-path", fullPath); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, const QString& value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "string"); addTextNode(value); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, bool value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "boolean"); addTextNode(value ? "true" : "false"); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, int value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "int"); addTextNode(QString::number(value)); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, double value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "double"); addTextNode(QString::number(value)); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, float value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "double"); addTextNode(QString::number(value)); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, long value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "long"); addTextNode(QString::number(value)); endElement(); } void KoXmlWriter::addConfigItem(const QString & configName, short value) { startElement("config:config-item"); addAttribute("config:name", configName); addAttribute("config:type", "short"); addTextNode(QString::number(value)); endElement(); } void KoXmlWriter::addTextSpan(const QString& text) { QMap tabCache; addTextSpan(text, tabCache); } void KoXmlWriter::addTextSpan(const QString& text, const QMap& tabCache) { int len = text.length(); int nrSpaces = 0; // number of consecutive spaces bool leadingSpace = false; QString str; str.reserve(len); // Accumulate chars either in str or in nrSpaces (for spaces). // Flush str when writing a subelement (for spaces or for another reason) // Flush nrSpaces when encountering two or more consecutive spaces for (int i = 0; i < len ; ++i) { QChar ch = text[i]; ushort unicode = ch.unicode(); if (unicode == ' ') { if (i == 0) leadingSpace = true; ++nrSpaces; } else { if (nrSpaces > 0) { // For the first space we use ' '. // "it is good practice to use (text:s) for the second and all following SPACE // characters in a sequence." (per the ODF spec) // however, per the HTML spec, "authors should not rely on user agents to render // white space immediately after a start tag or immediately before an end tag" // (and both we and OO.o ignore leading spaces in or elements...) if (!leadingSpace) { str += ' '; --nrSpaces; } if (nrSpaces > 0) { // there are more spaces if (!str.isEmpty()) addTextNode(str); str.clear(); startElement("text:s"); if (nrSpaces > 1) // it's 1 by default addAttribute("text:c", nrSpaces); endElement(); } } nrSpaces = 0; leadingSpace = false; switch (unicode) { case '\t': if (!str.isEmpty()) addTextNode(str); str.clear(); startElement("text:tab"); if (tabCache.contains(i)) addAttribute("text:tab-ref", tabCache[i] + 1); endElement(); break; // gracefully handle \f form feed in text input. // otherwise the xml will not be valid. // \f can be added e.g. in ascii import filter. case '\f': case '\n': case QChar::LineSeparator: if (!str.isEmpty()) addTextNode(str); str.clear(); startElement("text:line-break"); endElement(); break; default: // don't add stuff that is not allowed in xml. The stuff we need we have already handled above if (ch.unicode() >= 0x20) { str += text[i]; } break; } } } // either we still have text in str or we have spaces in nrSpaces if (!str.isEmpty()) { addTextNode(str); } if (nrSpaces > 0) { // there are more spaces startElement("text:s"); if (nrSpaces > 1) // it's 1 by default addAttribute("text:c", nrSpaces); endElement(); } } QIODevice *KoXmlWriter::device() const { return d->dev; } int KoXmlWriter::indentLevel() const { return d->tags.size() + d->baseIndentLevel; } QList KoXmlWriter::tagHierarchy() const { QList answer; Q_FOREACH (const Tag & tag, d->tags) answer.append(tag.tagName); return answer; } QString KoXmlWriter::toString() const { Q_ASSERT(!d->dev->isSequential()); if (d->dev->isSequential()) return QString(); bool wasOpen = d->dev->isOpen(); qint64 oldPos = -1; if (wasOpen) { oldPos = d->dev->pos(); if (oldPos > 0) d->dev->seek(0); } else { const bool openOk = d->dev->open(QIODevice::ReadOnly); Q_ASSERT(openOk); if (!openOk) return QString(); } QString s = QString::fromUtf8(d->dev->readAll()); if (wasOpen) d->dev->seek(oldPos); else d->dev->close(); return s; } diff --git a/libs/store/KoXmlWriter.h b/libs/store/KoXmlWriter.h index 10bfb40b29..bb8d532bbe 100644 --- a/libs/store/KoXmlWriter.h +++ b/libs/store/KoXmlWriter.h @@ -1,300 +1,285 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure Copyright (C) 2007 Thomas Zander Copyright (C) 2011 Lukáš Tvrdý This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef XMLWRITER_H #define XMLWRITER_H #include #include #include "kritastore_export.h" /** * A class for writing out XML (to any QIODevice), with a special attention on performance. * The XML is being written out along the way, which avoids requiring the entire - * document in memory (like QDom does), and avoids using QTextStream at all - * (which in Qt3 has major performance issues when converting to utf8). + * document in memory (like QDom does). */ class KRITASTORE_EXPORT KoXmlWriter { public: /** * Create a KoXmlWriter instance to write out an XML document into * the given QIODevice. */ explicit KoXmlWriter(QIODevice* dev, int indentLevel = 0); /// Destructor ~KoXmlWriter(); QIODevice *device() const; /** * Start the XML document. * This writes out the \ tag with utf8 encoding, and the DOCTYPE. * @param rootElemName the name of the root element, used in the DOCTYPE tag. * @param publicId the public identifier, e.g. "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" * @param systemId the system identifier, e.g. "office.dtd" or a full URL to it. */ void startDocument(const char* rootElemName, const char* publicId = 0, const char* systemId = 0); /// Call this to terminate an XML document. void endDocument(); /** * Start a new element, as a child of the current element. * @param tagName the name of the tag. Warning: this string must * remain alive until endElement, no copy is internally made. * Usually tagName is a string constant so this is no problem anyway. * @param indentInside if set to false, there will be no indentation inside * this tag. This is useful for elements where whitespace matters. */ void startElement(const char* tagName, bool indentInside = true); /** * Overloaded version of addAttribute( const char*, const char* ), * which is a bit slower because it needs to convert @p value to utf8 first. */ inline void addAttribute(const char* attrName, const QString& value) { addAttribute(attrName, value.toUtf8()); } /** * Add an attribute whose value is an integer */ inline void addAttribute(const char* attrName, int value) { addAttribute(attrName, QByteArray::number(value)); } /** * Add an attribute whose value is an unsigned integer */ inline void addAttribute(const char* attrName, uint value) { addAttribute(attrName, QByteArray::number(value)); } /** * Add an attribute whose value is an bool * It is written as "true" or "false" based on value */ inline void addAttribute(const char* attrName, bool value) { addAttribute(attrName, value ? "true" : "false"); } /** * Add an attribute whose value is a floating point number * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits) */ void addAttribute(const char* attrName, double value); /** * Add an attribute whose value is a floating point number * The number is written out with the highest possible precision * (unlike QString::number and setNum, which default to 6 digits) */ void addAttribute(const char* attrName, float value); - /** - * Add an attribute which represents a distance, measured in pt - * The number is written out with the highest possible precision - * (unlike QString::number and setNum, which default to 6 digits), - * and the unit name ("pt") is appended to it. - */ - void addAttributePt(const char* attrName, double value); - /** - * Add an attribute which represents a distance, measured in pt - * The number is written out with the highest possible precision - * (unlike QString::number and setNum, which default to 6 digits), - * and the unit name ("pt") is appended to it. - */ - void addAttributePt(const char* attrName, float value); /// Overloaded version of the one taking a const char* argument, for convenience void addAttribute(const char* attrName, const QByteArray& value); /** * Add an attribute to the current element. */ void addAttribute(const char* attrName, const char* value); /** * Terminate the current element. After this you should start a new one (sibling), * add a sibling text node, or close another one (end of siblings). */ void endElement(); /** * Overloaded version of addTextNode( const char* ), * which is a bit slower because it needs to convert @p str to utf8 first. */ inline void addTextNode(const QString& str) { addTextNode(str.toUtf8()); } /// Overloaded version of the one taking a const char* argument void addTextNode(const QByteArray& cstr); /** * @brief Adds a text node as a child of the current element. * * This is appends the literal content of @p str to the contents of the element. * E.g. addTextNode( "foo" ) inside a \ element gives \foo\, * and startElement( "b" ); endElement( "b" ); addTextNode( "foo" ) gives \\foo\ */ void addTextNode(const char* cstr); /** * @brief Adds a processing instruction * * This writes a processing instruction, like , where foo * is the target, and the rest is the data. * * Processing instructions are used in XML to keep processor-specific * information in the text of the document. */ void addProcessingInstruction(const char* cstr); /** * This is quite a special-purpose method, not for everyday use. * It adds a complete element (with its attributes and child elements) * as a child of the current element. The string is supposed to be escaped * for XML already, so it will usually come from another KoXmlWriter. */ void addCompleteElement(const char* cstr); /** * This is quite a special-purpose method, not for everyday use. * It adds a complete element (with its attributes and child elements) * as a child of the current element. The iodevice is supposed to be escaped * for XML already, so it will usually come from another KoXmlWriter. * This is usually used with KTempFile. */ void addCompleteElement(QIODevice* dev); // #### Maybe we want to subclass KoXmlWriter for manifest files. /** * Special helper for writing "manifest" files * This is equivalent to startElement/2*addAttribute/endElement * This API will probably have to change (or not be used anymore) * when we add support for encrypting/signing. * @note OASIS-specific */ void addManifestEntry(const QString& fullPath, const QString& mediaType); /** * Special helper for writing config item into settings.xml * @note OASIS-specific */ void addConfigItem(const QString & configName, const QString& value); /// @note OASIS-specific void addConfigItem(const QString & configName, bool value); /// @note OASIS-specific void addConfigItem(const QString & configName, int value); /// @note OASIS-specific void addConfigItem(const QString & configName, double value); /// @note OASIS-specific void addConfigItem(const QString & configName, float value); /// @note OASIS-specific void addConfigItem(const QString & configName, long value); /// @note OASIS-specific void addConfigItem(const QString & configName, short value); // TODO addConfigItem for datetime and base64Binary /** * @brief Adds a text span as nodes of the current element. * * Unlike KoXmlWriter::addTextNode it handles tabulations, linebreaks, * and multiple spaces by using the appropriate OASIS tags. * * @param text the text to write * * @note OASIS-specific */ void addTextSpan(const QString& text); /** * Overloaded version of addTextSpan which takes an additional tabCache map. * @param text the text to write * @param tabCache optional map allowing to find a tab for a given character index * @note OASIS-specific */ void addTextSpan(const QString& text, const QMap& tabCache); /** * @return the current indentation level. * Useful when creating a sub-KoXmlWriter (see addCompleteElement) */ int indentLevel() const; /** * Return all the open tags at this time, root element first. */ QList tagHierarchy() const; /** * Return the so far written XML as string for debugging purposes. */ QString toString() const; private: struct Tag { Tag(const char* t = 0, bool ind = true) : tagName(t), hasChildren(false), lastChildIsText(false), openingTagClosed(false), indentInside(ind) {} Tag(const Tag &original) { tagName = original.tagName; hasChildren = original.hasChildren; lastChildIsText = original.lastChildIsText; openingTagClosed = original.openingTagClosed; indentInside = original.indentInside; } const char* tagName; bool hasChildren : 1; ///< element or text children bool lastChildIsText : 1; ///< last child is a text node bool openingTagClosed : 1; ///< true once the '\>' in \ is written out bool indentInside : 1; ///< whether to indent the contents of this tag }; /// Write out \n followed by the number of spaces required. void writeIndent(); // writeCString is much faster than writeString. // Try to use it as much as possible, especially with constants. void writeString(const QString& str); // TODO check return value!!! inline void writeCString(const char* cstr) { device()->write(cstr, qstrlen(cstr)); } inline void writeChar(char c) { device()->putChar(c); } inline void closeStartElement(Tag& tag) { if (!tag.openingTagClosed) { tag.openingTagClosed = true; writeChar('>'); } } char* escapeForXML(const char* source, int length) const; bool prepareForChild(); void prepareForTextNode(); void init(); class Private; Private * const d; KoXmlWriter(const KoXmlWriter &); // forbidden KoXmlWriter& operator=(const KoXmlWriter &); // forbidden }; #endif /* XMLWRITER_H */ diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 70deb0c689..8bde174da4 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,827 +1,824 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif KisDlgInternalColorSelector::s_screenColorPickerFactory = KisScreenColorPicker::createScreenColorPicker; QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); - KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); + KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); - - // Indicate that it is now safe for users of KoResourcePaths to load resources - KoResourcePaths::setReady(); } void KisApplication::loadResources() { // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || !exportFileName.isEmpty()); const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed bool showmainWindow = !exportAs; // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index a37d91784f..719e2c4a7b 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2646 +1,2653 @@ /* 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 "kis_selection_manager.h" #ifdef HAVE_KIO #include #endif #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 "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_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "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 #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); 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); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; 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; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, 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); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); 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); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); 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) { 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(); - KisUpdateSchedulerConfigNotifier::instance()->notifyConfigChanged(); + 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(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (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") + "] "; } // new documents aren't saved yet, so we don't need to say it is modified // new files don't have a URL, so we are using that for the check if (!doc->url().isEmpty()) { if ( doc->isModified()) { caption += " [" + i18n("Modified") + "] "; } } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } 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(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::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; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::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!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (ret && !isExporting) { document->setRecovered(false); } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->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::dragEnterEvent(QDragEnterEvent *event) { d->welcomePage->showDropAreaIndicator(true); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { d->welcomePage->showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = installBundle(url.toLocalFile()); qDebug() << "\t" << r; } else { openDocument(url, None); } } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { d->welcomePage->showDropAreaIndicator(false); 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"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { 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; 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 (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_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { + /** + * Notify selection manager so that it could update selection mask overlay + */ + viewManager()->selectionManager()->selectionChanged(); + + auto *kisPart = KisPart::instance(); auto *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(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } 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(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } 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_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); 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->createAction("file_close"); connect(d->close, SIGNAL(triggered()), 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 componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::moveEvent(QMoveEvent *e) { /** * For checking if the display number has changed or not we should always use * positional overload, not using QWidget overload. Otherwise we might get * inconsistency, because screenNumber(widget) can return -1, but screenNumber(pos) * will always return the nearest screen. */ const int oldScreen = qApp->desktop()->screenNumber(e->oldPos()); const int newScreen = qApp->desktop()->screenNumber(e->pos()); if (oldScreen != newScreen) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index 7a3a9c2c20..ab49955b06 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,1016 +1,1028 @@ /* * Copyright (C) 2014 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 "KisView.h" #include "KisView_p.h" #include #include #include #include "KoDocumentInfo.h" #include "KoPageLayout.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_config.h" #include "KisDocument.h" #include "kis_image_manager.h" #include "KisMainWindow.h" #include "kis_mimedata.h" #include "kis_mirror_axis.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_shape_controller.h" #include "kis_tool_freehand.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" #include "KisReferenceImagesDecoration.h" #include "kis_progress_widget.h" #include "kis_signal_compressor.h" #include "kis_filter_manager.h" #include "kis_file_layer.h" #include "krita_utils.h" #include "input/kis_input_manager.h" #include "KisRemoteFileFetcher.h" +#include "kis_selection_manager.h" //static QString KisView::newObjectName() { static int s_viewIFNumber = 0; QString name; name.setNum(s_viewIFNumber++); name.prepend("view_"); return name; } bool KisView::s_firstView = true; class Q_DECL_HIDDEN KisView::Private { public: Private(KisView *_q, KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection) : actionCollection(actionCollection) , viewConverter() , canvasController(_q, actionCollection) , canvas(&viewConverter, resourceManager, _q, document->shapeController()) , zoomManager(_q, &this->viewConverter, &this->canvasController) , paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q)) , referenceImagesDecoration(new KisReferenceImagesDecoration(_q, document)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } bool inOperation; //in the middle of an operation (no screen refreshing)? QPointer document; // our KisDocument QWidget *tempActiveWidget = 0; /** * Signals the document has been deleted. Can't use document==0 since this * only happens in ~QObject, and views get deleted by ~KisDocument. * XXX: either provide a better justification to do things this way, or * rework the mechanism. */ bool documentDeleted = false; KActionCollection* actionCollection; KisCoordinatesConverter viewConverter; KisCanvasController canvasController; KisCanvas2 canvas; KisZoomManager zoomManager; KisViewManager *viewManager = 0; KisNodeSP currentNode; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration; KisReferenceImagesDecorationSP referenceImagesDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; QMdiSubWindow *subWindow{nullptr}; bool softProofing = false; bool gamutCheck = false; // Hmm sorry for polluting the private class with such a big inner class. // At the beginning it was a little struct :) class StatusBarItem { public: StatusBarItem(QWidget * widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_connected(false), m_hidden(false) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void ensureItemShown(QStatusBar * sb) { Q_ASSERT(m_widget); if (!m_connected) { if (m_permanent) sb->addPermanentWidget(m_widget, m_stretch); else sb->addWidget(m_widget, m_stretch); if(!m_hidden) m_widget->show(); m_connected = true; } } void ensureItemHidden(QStatusBar * sb) { if (m_connected) { m_hidden = m_widget->isHidden(); sb->removeWidget(m_widget); m_widget->hide(); m_connected = false; } } private: QWidget * m_widget = 0; int m_stretch; bool m_permanent; bool m_connected = false; bool m_hidden = false; }; }; KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent) : QWidget(parent) , d(new Private(this, document, resourceManager, actionCollection)) { Q_ASSERT(document); connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool))); setObjectName(newObjectName()); d->document = document; setFocusPolicy(Qt::StrongFocus); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), this, SLOT(slotSavingStatusMessage(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg(false); d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVastScrolling(cfg.vastScrolling()); d->canvasController.setCanvas(&d->canvas); d->zoomManager.setup(d->actionCollection); connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged())); setAcceptDrops(true); connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished())); d->canvas.addDecoration(d->referenceImagesDecoration); d->referenceImagesDecoration->setVisible(true); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); } KisView::~KisView() { if (d->viewManager) { if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } d->viewManager->mainWindow()->notifyChildViewDestroyed(this); } KoToolManager::instance()->removeCanvasController(&d->canvasController); d->canvasController.setCanvas(0); KisPart::instance()->removeView(this); delete d; } void KisView::notifyCurrentStateChanged(bool isCurrent) { d->isCurrent = isCurrent; if (!d->isCurrent && d->savedFloatingMessage) { d->savedFloatingMessage->removeMessage(); } KisInputManager *inputManager = globalInputManager(); if (d->isCurrent) { inputManager->attachPriorityEventFilter(&d->canvasController); } else { inputManager->detachPriorityEventFilter(&d->canvasController); } + + /** + * When current view is changed, currently selected node is also changed, + * therefore we should update selection overlay mask + */ + viewManager()->selectionManager()->selectionChanged(); +} + +bool KisView::isCurrent() const +{ + return d->isCurrent; } void KisView::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisView::showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->viewManager) return; if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) { if (d->savedFloatingMessage) { d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment); } else { d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment); d->savedFloatingMessage->setShowOverParent(true); d->savedFloatingMessage->setIcon(icon); connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage())); d->floatingMessageCompressor.start(); } } } bool KisView::canvasIsMirrored() const { return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored(); } void KisView::setViewManager(KisViewManager *view) { d->viewManager = view; KoToolManager::instance()->addController(&d->canvasController); KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController); dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas); if (resourceProvider()) { resourceProvider()->slotImageSizeChanged(); } if (d->viewManager && d->viewManager->nodeManager()) { d->viewManager->nodeManager()->nodesUpdated(); } connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&))); connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged())); // executed in a context of an image thread connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), SLOT(slotImageNodeAdded(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)), SLOT(slotContinueAddNode(KisNodeSP)), Qt::AutoConnection); // executed in a context of an image thread connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(slotImageNodeRemoved(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)), SLOT(slotContinueRemoveNode(KisNodeSP)), Qt::AutoConnection); d->viewManager->updateGUI(); KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } KisViewManager* KisView::viewManager() const { return d->viewManager; } void KisView::slotImageNodeAdded(KisNodeSP node) { emit sigContinueAddNode(node); } void KisView::slotContinueAddNode(KisNodeSP newActiveNode) { /** * When deleting the last layer, root node got selected. We should * fix it when the first layer is added back. * * Here we basically reimplement what Qt's view/model do. But * since they are not connected, we should do it manually. */ if (!d->isCurrent && (!d->currentNode || !d->currentNode->parent())) { d->currentNode = newActiveNode; } } void KisView::slotImageNodeRemoved(KisNodeSP node) { emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node)); } void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode) { if (!d->isCurrent) { d->currentNode = newActiveNode; } } KoZoomController *KisView::zoomController() const { return d->zoomManager.zoomController(); } KisZoomManager *KisView::zoomManager() const { return &d->zoomManager; } KisCanvasController *KisView::canvasController() const { return &d->canvasController; } KisCanvasResourceProvider *KisView::resourceProvider() const { if (d->viewManager) { return d->viewManager->resourceProvider(); } return 0; } KisInputManager* KisView::globalInputManager() const { return d->viewManager ? d->viewManager->inputManager() : 0; } KisCanvas2 *KisView::canvasBase() const { return &d->canvas; } KisImageWSP KisView::image() const { if (d->document) { return d->document->image(); } return 0; } KisCoordinatesConverter *KisView::viewConverter() const { return &d->viewConverter; } void KisView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node")) { event->accept(); // activate view if it should accept the drop this->setFocus(); } else { event->ignore(); } } void KisView::dropEvent(QDropEvent *event) { KisImageWSP kisimage = image(); Q_ASSERT(kisimage); QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint(); QRect imageBounds = kisimage->bounds(); QPoint pasteCenter; bool forceRecenter; if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(cursorPos)) { pasteCenter = cursorPos; forceRecenter = true; } else { pasteCenter = imageBounds.center(); forceRecenter = false; } if (event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasImage()) { KisShapeController *kritaShapeController = dynamic_cast(d->document->shapeController()); QList nodes = KisMimeData::loadNodes(event->mimeData(), imageBounds, pasteCenter, forceRecenter, kisimage, kritaShapeController); Q_FOREACH (KisNodeSP node, nodes) { if (node) { KisNodeCommandsAdapter adapter(viewManager()); if (!viewManager()->nodeManager()->activeLayer()) { adapter.addNode(node, kisimage->rootLayer() , 0); } else { adapter.addNode(node, viewManager()->nodeManager()->activeLayer()->parent(), viewManager()->nodeManager()->activeLayer()); } } } } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); if (urls.length() > 0) { QMenu popup; popup.setObjectName("drop_popup"); QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup); QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup); QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup); QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup); QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup); QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup); QAction *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup); QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); popup.addAction(insertAsReferenceImage); popup.addAction(insertManyLayers); popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); popup.addAction(insertAsReferenceImages); insertAsNewLayer->setEnabled(image() && urls.count() == 1); insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); insertAsReferenceImage->setEnabled(image() && urls.count() == 1); insertManyLayers->setEnabled(image() && urls.count() > 1); insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); insertAsReferenceImages->setEnabled(image() && urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; for (QUrl url : urls) { if (!url.isLocalFile()) { // download the file and substitute the url KisRemoteFileFetcher fetcher; tmp = new QTemporaryFile(); tmp->setAutoRemove(true); if (!fetcher.fetchFile(url, tmp)) { qDebug() << "Fetching" << url << "failed"; continue; } url = url.fromLocalFile(tmp->fileName()); } if (url.isLocalFile()) { if (action == insertAsNewLayer || action == insertManyLayers) { d->viewManager->imageManager()->importImage(url); activateWindow(); } else if (action == insertAsNewFileLayer || action == insertManyFileLayers) { KisNodeCommandsAdapter adapter(viewManager()); KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(), KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8); adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode()); } else if (action == openInNewDocument || action == openManyDocuments) { if (mainWindow()) { mainWindow()->openDocument(url, KisMainWindow::None); } } else if (action == insertAsReferenceImage || action == insertAsReferenceImages) { auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter, this); if (reference) { reference->setPosition(d->viewConverter.imageToDocument(cursorPos)); d->referenceImagesDecoration->addReferenceImage(reference); KoToolManager::instance()->switchToolRequested("ToolReferenceImages"); } } } delete tmp; tmp = 0; } } } } } KisDocument *KisView::document() const { return d->document; } void KisView::setDocument(KisDocument *document) { d->document->disconnect(this); d->document = document; QStatusBar *sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), this, SLOT(slotSavingStatusMessage(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } } void KisView::setDocumentDeleted() { d->documentDeleted = true; } QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent) { Q_UNUSED(parent); QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this); printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage()); printDialog->setEnabledOptions(printJob->printDialogOptions()); return printDialog; } KisMainWindow * KisView::mainWindow() const { return dynamic_cast(window()); } void KisView::setSubWindow(QMdiSubWindow *subWindow) { d->subWindow = subWindow; } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving) { QStatusBar *sb = statusBar(); if (sb) { sb->showMessage(text, timeout); } KisConfig cfg(true); if (!sb || sb->isHidden() || (!isAutoSaving && cfg.forceShowSaveMessages()) || (cfg.forceShowAutosaveMessages() && isAutoSaving)) { viewManager()->showFloatingMessage(text, QIcon()); } } void KisView::slotClearStatusText() { QStatusBar *sb = statusBar(); if (sb) { sb->clearMessage(); } } QList KisView::createChangeUnitActions(bool addPixelUnit) { UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this); return unitActions->actions(); } void KisView::closeEvent(QCloseEvent *event) { // Check whether we're the last (user visible) view int viewCount = KisPart::instance()->viewCount(document()); if (viewCount > 1 || !isVisible()) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { d->viewManager->statusBar()->setView(0); event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; document()->waitForSavingToComplete(); if (document()->isModified()) { QString name; if (document()->documentInfo()) { name = document()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = document()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : { bool isNative = (document()->mimeType() == document()->nativeFormatMimeType()); if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false)) return false; break; } case QMessageBox::No : { KisImageSP image = document()->image(); image->requestStrokeCancellation(); viewManager()->blockUntilOperationsFinishedForced(image); document()->removeAutoSaveFiles(); document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; } default : // case QMessageBox::Cancel : return false; } } return true; } void KisView::resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint, const QPointF &newImageStillPoint) { const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter(); QPointF oldPreferredCenter = d->canvasController.preferredCenter(); /** * Calculating the still point in old coordinates depending on the * parameters given */ QPointF oldStillPoint; if (changeCentering) { oldStillPoint = converter->imageToWidget(oldImageStillPoint) + converter->documentOffset(); } else { QSize oldDocumentSize = d->canvasController.documentSize(); oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height()); } /** * Updating the document size */ QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes()); KoZoomController *zc = d->zoomManager.zoomController(); zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom()); zc->setPageSize(size); zc->setDocumentSize(size, true); /** * Calculating the still point in new coordinates depending on the * parameters given */ QPointF newStillPoint; if (changeCentering) { newStillPoint = converter->imageToWidget(newImageStillPoint) + converter->documentOffset(); } else { QSize newDocumentSize = d->canvasController.documentSize(); newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height()); } d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint); } void KisView::syncLastActiveNodeToDocument() { KisDocument *doc = document(); if (doc) { doc->setPreActivatedNode(d->currentNode); } } void KisView::saveViewState(KisPropertiesConfiguration &config) const { config.setProperty("file", d->document->url()); config.setProperty("window", mainWindow()->windowStateConfig().name()); if (d->subWindow) { config.setProperty("geometry", d->subWindow->saveGeometry().toBase64()); } config.setProperty("zoomMode", (int)zoomController()->zoomMode()); config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom()); d->canvasController.saveCanvasState(config); } void KisView::restoreViewState(const KisPropertiesConfiguration &config) { if (d->subWindow) { QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1()); d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry)); } qreal zoom = config.getFloat("zoom", 1.0f); int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE); d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom); d->canvasController.restoreCanvasState(config); } void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; d->canvas.slotTrySwitchShapeManager(); syncLastActiveNodeToDocument(); } KisNodeSP KisView::currentNode() const { return d->currentNode; } KisLayerSP KisView::currentLayer() const { KisNodeSP node; KisMaskSP mask = currentMask(); if (mask) { node = mask->parent(); } else { node = d->currentNode; } return qobject_cast(node.data()); } KisMaskSP KisView::currentMask() const { return dynamic_cast(d->currentNode.data()); } KisSelectionSP KisView::selection() { KisLayerSP layer = currentLayer(); if (layer) return layer->selection(); // falls through to the global // selection, or 0 in the end if (image()) { return image()->globalSelection(); } return 0; } void KisView::slotSoftProofing(bool softProofing) { d->softProofing = softProofing; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Soft Proofing doesn't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (softProofing){ message = i18n("Soft Proofing turned on."); } else { message = i18n("Soft Proofing turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotSoftProofing(softProofing); } void KisView::slotGamutCheck(bool gamutCheck) { d->gamutCheck = gamutCheck; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Gamut Warnings don't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (gamutCheck){ message = i18n("Gamut Warnings turned on."); if (!d->softProofing){ message += "\n "+i18n("But Soft Proofing is still off."); } } else { message = i18n("Gamut Warnings turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotGamutCheck(gamutCheck); } bool KisView::softProofing() { return d->softProofing; } bool KisView::gamutCheck() { return d->gamutCheck; } void KisView::slotLoadingFinished() { if (!document()) return; /** * Cold-start of image size/resolution signals */ slotImageResolutionChanged(); if (image()->locked()) { // If this is the first view on the image, the image will have been locked // so unlock it. image()->blockSignals(false); image()->unlock(); } canvasBase()->initializeImage(); /** * Dirty hack alert */ d->zoomManager.zoomController()->setAspectMode(true); if (viewConverter()) { viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE); } connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*))); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF))); KisNodeSP activeNode = document()->preActivatedNode(); if (!activeNode) { activeNode = image()->rootLayer()->lastChild(); } while (activeNode && !activeNode->inherits("KisLayer")) { activeNode = activeNode->prevSibling(); } setCurrentNode(activeNode); zoomManager()->updateImageBoundsSnapping(); } void KisView::slotSavingFinished() { if (d->viewManager && d->viewManager->mainWindow()) { d->viewManager->mainWindow()->updateCaption(); } } KisPrintJob * KisView::createPrintJob() { return new KisPrintJob(image()); } void KisView::slotImageResolutionChanged() { resetImageSizeAndScroll(false); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); // update KoUnit value for the document if (resourceProvider()) { resourceProvider()->resourceManager()-> setResource(KoCanvasResourceManager::Unit, d->canvas.unit()); } } void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint) { resetImageSizeAndScroll(true, oldStillPoint, newStillPoint); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); } void KisView::closeView() { d->subWindow->close(); } diff --git a/libs/ui/KisView.h b/libs/ui/KisView.h index 2b764953ce..a33127a0ab 100644 --- a/libs/ui/KisView.h +++ b/libs/ui/KisView.h @@ -1,293 +1,294 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2007 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. */ #ifndef KIS_VIEW_H #define KIS_VIEW_H #include #include #include #include #include "kritaui_export.h" #include "widgets/kis_floating_message.h" class KisDocument; class KisMainWindow; class KisPrintJob; class KisCanvasController; class KisZoomManager; class KisCanvas2; class KisViewManager; class KisDocument; class KisCanvasResourceProvider; class KisCoordinatesConverter; class KisInputManager; class KoZoomController; class KoZoomController; struct KoPageLayout; class KoCanvasResourceManager; // KDE classes class QAction; class KActionCollection; class KConfigGroup; // Qt classes class QDragEnterEvent; class QDropEvent; class QPrintDialog; class QCloseEvent; class QStatusBar; class QMdiSubWindow; /** * This class is used to display a @ref KisDocument. * * Multiple views can be attached to one document at a time. */ class KRITAUI_EXPORT KisView : public QWidget { Q_OBJECT public: /** * Creates a new view for the document. */ KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent = 0); ~KisView() override; // Temporary while teasing apart view and mainwindow void setViewManager(KisViewManager *view); KisViewManager *viewManager() const; public: /** * Retrieves the document object of this view. */ KisDocument *document() const; /** * Reset the view to show the given document. */ void setDocument(KisDocument *document); /** * Tells this view that its document has got deleted (called internally) */ void setDocumentDeleted(); /** * In order to print the document represented by this view a new print job should * be constructed that is capable of doing the printing. * The default implementation returns 0, which silently cancels printing. */ KisPrintJob * createPrintJob(); /** * Create a QPrintDialog based on the @p printJob */ QPrintDialog *createPrintDialog(KisPrintJob *printJob, QWidget *parent); /** * @return the KisMainWindow in which this view is currently. */ KisMainWindow *mainWindow() const; /** * Tells this view which subwindow it is part of. */ void setSubWindow(QMdiSubWindow *subWindow); /** * @return the statusbar of the KisMainWindow in which this view is currently. */ QStatusBar *statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); /** * Return the zoomController for this view. */ KoZoomController *zoomController() const; /// create a list of actions that when activated will change the unit on the document. QList createChangeUnitActions(bool addPixelUnit = false); void closeView(); public: /** * The zoommanager handles everything action-related to zooming */ KisZoomManager *zoomManager() const; /** * The CanvasController decorates the canvas with scrollbars * and knows where to start painting on the canvas widget, i.e., * the document offset. */ KisCanvasController *canvasController() const; KisCanvasResourceProvider *resourceProvider() const; /** * Filters events and sends them to canvas actions. Shared * among all the views/canvases * * NOTE: May be null while initialization! */ KisInputManager* globalInputManager() const; /** * @return the canvas object */ KisCanvas2 *canvasBase() const; /// @return the image this view is displaying KisImageWSP image() const; KisCoordinatesConverter *viewConverter() const; void resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint = QPointF(), const QPointF &newImageStillPoint = QPointF()); void setCurrentNode(KisNodeSP node); KisNodeSP currentNode() const; KisLayerSP currentLayer() const; KisMaskSP currentMask() const; /** * @brief softProofing * @return whether or not we're softproofing in this view. */ bool softProofing(); /** * @brief gamutCheck * @return whether or not we're using gamut warnings in this view. */ bool gamutCheck(); /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); void notifyCurrentStateChanged(bool isCurrent); + bool isCurrent() const; void setShowFloatingMessage(bool show); void showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment); bool canvasIsMirrored() const; void syncLastActiveNodeToDocument(); void saveViewState(KisPropertiesConfiguration &config) const; void restoreViewState(const KisPropertiesConfiguration &config); public Q_SLOTS: /** * Display a message in the status bar (calls QStatusBar::message()) * @todo rename to something more generic * @param value determines autosaving */ void slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving = false); /** * End of the message in the status bar (calls QStatusBar::clear()) * @todo rename to something more generic */ void slotClearStatusText(); /** * @brief slotSoftProofing set whether or not we're softproofing in this view. * Will be setting the same in the canvas belonging to the view. */ void slotSoftProofing(bool softProofing); /** * @brief slotGamutCheck set whether or not we're gamutchecking in this view. * Will be setting the same in the vans belonging to the view. */ void slotGamutCheck(bool gamutCheck); bool queryClose(); private Q_SLOTS: void slotImageNodeAdded(KisNodeSP node); void slotContinueAddNode(KisNodeSP newActiveNode); void slotImageNodeRemoved(KisNodeSP node); void slotContinueRemoveNode(KisNodeSP newActiveNode); Q_SIGNALS: // From KisImage void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void titleModified(QString,bool); void sigContinueAddNode(KisNodeSP newActiveNode); void sigContinueRemoveNode(KisNodeSP newActiveNode); protected: // QWidget overrides void dragEnterEvent(QDragEnterEvent * event) override; void dropEvent(QDropEvent * event) override; void closeEvent(QCloseEvent *event) override; /** * Generate a name for this view. */ QString newObjectName(); public Q_SLOTS: void slotLoadingFinished(); void slotSavingFinished(); void slotImageResolutionChanged(); void slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); private: class Private; Private * const d; static bool s_firstView; }; #endif diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index 2a56c9fab5..5138e0fec3 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1396 +1,1396 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 2011 José Luis Vergara * 2017 L. E. Segovia * * 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 "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "dialogs/kis_dlg_blacklist_cleanup.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include #include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "KisDecorationsManager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include "KisPrintJob.h" #include #include "KisResourceServerProvider.h" #include "kis_selection.h" +#include "kis_selection_mask.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" #include #include #include "kis_signals_blocker.h" class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , actionAuthor(0) , showPixelGrid(0) { KisViewManager::initializeResourceManager(&canvasResourceManager); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; QPointer persistentImageProgressUpdater; QScopedPointer persistentUnthreadedProgressUpdaterRouter; QPointer persistentUnthreadedProgressUpdater; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisDecorationsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceManager canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KSelectAction *actionAuthor; // Select action for author profile. KisAction *showPixelGrid; QByteArray canvasState; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QFlags windowFlags; #endif bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); d->persistentImageProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentImageProgressUpdater->setRange(0,100); d->persistentImageProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentUnthreadedProgressUpdater->setRange(0,100); d->persistentUnthreadedProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdaterRouter.reset( new KoProgressUpdater(d->persistentUnthreadedProgressUpdater, KoProgressUpdater::Unthreaded)); d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true); d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), resourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg(true); d->showFloatingMessage = cfg.showCanvasMessages(); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } KisViewManager::~KisViewManager() { KisConfig cfg(false); if (resourceProvider() && resourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name()); cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength()); delete d; } void KisViewManager::initializeResourceManager(KoCanvasResourceManager *resourceManager) { resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { // WARNING: this slot is called even when a view from another main windows is added! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { // WARNING: this slot is called even when a view from another main windows is removed! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater); doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } QPointer imageView = qobject_cast(view); d->currentImageView = imageView; if (imageView) { d->softProof->setChecked(imageView->softProofing()); d->gamutCheck->setChecked(imageView->gamutCheck()); // Wait for the async image to have loaded KisDocument* doc = view->document(); if (KisConfig(true).readEntry("EnablePositionLabel", false)) { connect(d->currentImageView->canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), &d->statusBar, SLOT(documentMousePositionChanged(QPointF))); } // Restore the last used brush preset, color and background color. if (first) { KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QString defaultPresetName = "basic_tip_default"; bool foundTip = false; for (int i=0; iresourceCount(); i++) { KisPaintOpPresetSP resource = rserver->resources().at(i); if (resource->name().toLower().contains("basic_tip_default")) { defaultPresetName = resource->name(); foundTip = true; } else if (foundTip == false && (resource->name().toLower().contains("default") || resource->filename().toLower().contains("default"))) { defaultPresetName = resource->name(); foundTip = true; } } KisConfig cfg(true); QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName(defaultPresetName); } if (!preset && !rserver->resources().isEmpty()) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); } } KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); // set up progrress reporting doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater); d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation())); d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool))); imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), resourceProvider(), SLOT(slotImageSizeChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigResolutionChanged(double,double)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(updateGUI())); d->viewConnections.addUniqueConnection( d->currentImageView->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); } d->actionManager.updateGUI(); resourceProvider()->slotImageSizeChanged(); resourceProvider()->slotOnScreenResolutionChanged(); Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::resourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } QPointer KisViewManager::createUnthreadedUpdater(const QString &name) { return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false); } QPointer KisViewManager::createThreadedUpdater(const QString &name) { return d->statusBar.progressUpdater()->startSubtask(1, name, false); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { - KoProperties properties; - QList masks = layer->childNodes(QStringList("KisSelectionMask"), properties); - if (masks.size() == 1) { - return masks[0]->isEditable(); + KisSelectionMaskSP mask = layer->selectionMask(); + if (mask) { + return mask->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg(true); d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } a = actionManager()->createAction("edit_blacklist_cleanup"); connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup())); actionManager()->createAction("ruler_pixel_multiple2"); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); d->showPixelGrid = actionManager()->createAction("view_pixel_grid"); slotUpdatePixelGridAction(); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } void KisViewManager::slotBlacklistCleanup() { KisDlgBlacklistCleanup dialog; dialog.exec(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); dialog.blockIfImageIsBusy(); return dialog.result() == QDialog::Accepted; } bool KisViewManager::blockUntilOperationsFinished(KisImageSP image) { return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image) { d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { KisDocument *srcDoc = document(); if (!srcDoc) return; if (!this->blockUntilOperationsFinished(srcDoc->image())) return; KisDocument *doc = 0; { KisImageBarrierLocker l(srcDoc->image()); doc = srcDoc->clone(); } KIS_SAFE_ASSERT_RECOVER_RETURN(doc); QString name = srcDoc->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); return; } bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { // TODO: this will not work with files extensions like jp2 // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); fileAlreadyExists = QFile(fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } document()->setFileBatchMode(true); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); return; } bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = document()->localFilePath(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg(false); cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg(false); KisMainWindow* main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } if (toggled) { d->canvasState = qtMainWindow()->saveState(); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) d->windowFlags = main->windowState(); #endif } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); if (action) { action->setCheckable(true); if (toggled) { if (action->isChecked()) { cfg.setShowDockers(action->isChecked()); action->setChecked(false); } else { cfg.setShowDockers(false); } } else { action->setChecked(cfg.showDockers()); } } } // QT in windows does not return to maximized upon 4th tab in a row // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/ if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // If window was maximized prior to fullscreen, restore that if (d->windowFlags & Qt::WindowMaximized) { main->setWindowState( main->windowState() | Qt::WindowMaximized); } #endif } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg(true); d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGUI(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg(true); bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg(false); cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg(false); cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", ""); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"; QStringList filters = QStringList() << "*.authorinfo"; QDir dir(authorInfo); Q_FOREACH(QString entry, dir.entryList(filters)) { int ln = QString(".authorinfo").size(); entry.chop(ln); if (!profiles.contains(entry)) { profiles.append(entry); } } Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous" || profileName.isEmpty()) { d->actionAuthor->setCurrentItem(0); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } } void KisViewManager::slotUpdatePixelGridAction() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid); KisSignalsBlocker b(d->showPixelGrid); KisConfig cfg(true); d->showPixelGrid->setChecked(cfg.pixelGridEnabled()); } diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index a8323bb269..1c46ff11bc 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,399 +1,404 @@ /* * 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 "kis_canvas_controller.h" #include #include #include #include - +#include #include "kis_canvas_decoration.h" #include "kis_paintop_transformation_connector.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "opengl/kis_opengl_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_config.h" #include "kis_signal_compressor_with_param.h" #include "kis_config_notifier.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) : q(qq), paintOpTransformationConnector(0) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; KisPaintopTransformationConnector *paintOpTransformationConnector; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { if (canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); m_d->paintOpTransformationConnector = new KisPaintopTransformationConnector(kritaCanvas, this); } else { m_d->coordinatesConverter = 0; delete m_d->paintOpTransformationConnector; m_d->paintOpTransformationConnector = 0; } KoCanvasControllerWidget::setCanvas(canvas); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } QPointF KisCanvasController::currentCursorPosition() const { KoCanvasBase *canvas = m_d->view->canvasBase(); QWidget *canvasWidget = canvas->canvasWidget(); const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos()); return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); } else if (event->type() == QEvent::FocusIn) { m_d->view->syncLastActiveNodeToDocument(); } return false; } void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle, const QPointF ¢er) { QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvas(qreal angle) { rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint()); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } qreal KisCanvasController::rotation() const { return m_d->coordinatesConverter->rotationAngle(); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } + else if (value) { + QAction *action = m_d->view->viewManager()->actionCollection()->action("wrap_around_mode"); + QString shortcut = action ? action->shortcut().toString() : "W"; + m_d->view->viewManager()->showFloatingMessage(i18n("Entering Wraparound mode. Press '%1' to leave Wraparound mode.", shortcut), QIcon()); + } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotTogglePixelGrid(bool value) { KisConfig cfg(false); cfg.enablePixelGrid(value); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); bool result = levelOfDetailMode(); if (!value || result) { m_d->view->viewManager()->showFloatingMessage( i18n("Instant Preview Mode: %1", result ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } else { QString reason; if (!kritaCanvas->canvasIsOpenGL()) { reason = i18n("Instant Preview is only supported with OpenGL activated"); } else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { QString filteringMode = kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? i18n("Bilinear") : i18n("Nearest Neighbour"); reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode); } m_d->view->viewManager()->showFloatingMessage( i18n("Failed activating Instant Preview mode!\n\n%1", reason), QIcon(), 5000, KisFloatingMessage::Low); } } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } void KisCanvasController::saveCanvasState(KisPropertiesConfiguration &config) const { const QPointF ¢er = preferredCenter(); config.setProperty("panX", center.x()); config.setProperty("panY", center.y()); config.setProperty("rotation", rotation()); config.setProperty("mirror", m_d->coordinatesConverter->xAxisMirrored()); config.setProperty("wrapAround", wrapAroundMode()); config.setProperty("enableInstantPreview", levelOfDetailMode()); } void KisCanvasController::restoreCanvasState(const KisPropertiesConfiguration &config) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); mirrorCanvas(config.getBool("mirror", false)); rotateCanvas(config.getFloat("rotation", 0.0f)); const QPointF ¢er = preferredCenter(); float panX = config.getFloat("panX", center.x()); float panY = config.getFloat("panY", center.y()); setPreferredCenter(QPointF(panX, panY)); slotToggleWrapAroundMode(config.getBool("wrapAround", false)); kritaCanvas->setLodAllowedInCanvas(config.getBool("enableInstantPreview", false)); } void KisCanvasController::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. KisDocument *doc = m_d->view->document(); if (!doc) return; QRectF documentBounds = doc->documentBounds(); QRectF viewRect = m_d->coordinatesConverter->imageToWidget(documentBounds); // Cancel out any existing pan const QRectF imageBounds = m_d->view->image()->bounds(); const QRectF imageBB = m_d->coordinatesConverter->imageToWidget(imageBounds); QPointF pan = imageBB.topLeft(); viewRect.translate(-pan); int drawH = viewport()->height(); int drawW = viewport()->width(); qreal horizontalReserve = vastScrollingFactor() * drawW; qreal verticalReserve = vastScrollingFactor() * drawH; qreal xMin = viewRect.left() - horizontalReserve; qreal yMin = viewRect.top() - verticalReserve; qreal xMax = viewRect.right() - drawW + horizontalReserve; qreal yMax = viewRect.bottom() - drawH + verticalReserve; QScrollBar *hScroll = horizontalScrollBar(); QScrollBar *vScroll = verticalScrollBar(); hScroll->setRange(static_cast(xMin), static_cast(xMax)); vScroll->setRange(static_cast(yMin), static_cast(yMax)); int fontHeight = QFontMetrics(font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontHeight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontHeight); } diff --git a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp index 6e5054d161..38585af521 100644 --- a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp @@ -1,147 +1,147 @@ /* * 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 "KisAsyncAnimationCacheRenderDialog.h" #include "KisAsyncAnimationCacheRenderer.h" #include "kis_animation_frame_cache.h" #include #include #include namespace { QList calcDirtyFramesList(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange) { QList result; KisImageSP image = cache->image(); if (!image) return result; KisImageAnimationInterface *animation = image->animationInterface(); if (!animation->hasAnimation()) return result; if (playbackRange.isValid()) { KIS_ASSERT_RECOVER_RETURN_VALUE(!playbackRange.isInfinite(), result); // TODO: optimize check for fully-cached case for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) { - KisTimeRange stillFrameRange = KisTimeRange::infinite(0); - KisTimeRange::calculateTimeRangeRecursive(image->root(), frame, stillFrameRange, true); + const KisTimeRange stillFrameRange = + KisTimeRange::calculateIdenticalFramesRecursive(image->root(), frame); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(stillFrameRange.isValid(), result); if (cache->frameStatus(stillFrameRange.start()) == KisAnimationFrameCache::Uncached) { result.append(stillFrameRange.start()); } if (stillFrameRange.isInfinite()) { break; } else { frame = stillFrameRange.end(); } } } return result; } } int KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange, const KisTimeRange &skipRange) { int result = -1; KisImageSP image = cache->image(); if (!image) return result; KisImageAnimationInterface *animation = image->animationInterface(); if (!animation->hasAnimation()) return result; if (playbackRange.isValid()) { KIS_ASSERT_RECOVER_RETURN_VALUE(!playbackRange.isInfinite(), result); // TODO: optimize check for fully-cached case for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) { if (skipRange.contains(frame)) { if (skipRange.isInfinite()) { break; } else { frame = skipRange.end(); continue; } } if (cache->frameStatus(frame) != KisAnimationFrameCache::Cached) { result = frame; break; } } } return result; } struct KisAsyncAnimationCacheRenderDialog::Private { Private(KisAnimationFrameCacheSP _cache, const KisTimeRange &_range) : cache(_cache), range(_range) { } KisAnimationFrameCacheSP cache; KisTimeRange range; }; KisAsyncAnimationCacheRenderDialog::KisAsyncAnimationCacheRenderDialog(KisAnimationFrameCacheSP cache, const KisTimeRange &range, int busyWait) : KisAsyncAnimationRenderDialogBase(i18n("Regenerating cache..."), cache->image(), busyWait), m_d(new Private(cache, range)) { } KisAsyncAnimationCacheRenderDialog::~KisAsyncAnimationCacheRenderDialog() { } QList KisAsyncAnimationCacheRenderDialog::calcDirtyFrames() const { return calcDirtyFramesList(m_d->cache, m_d->range); } KisAsyncAnimationRendererBase *KisAsyncAnimationCacheRenderDialog::createRenderer(KisImageSP image) { Q_UNUSED(image); return new KisAsyncAnimationCacheRenderer(); } void KisAsyncAnimationCacheRenderDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) { Q_UNUSED(image); Q_UNUSED(frame); KisAsyncAnimationCacheRenderer *cacheRenderer = dynamic_cast(renderer); KIS_SAFE_ASSERT_RECOVER_RETURN(cacheRenderer); cacheRenderer->setFrameCache(m_d->cache); } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 8fad0705f4..3c25d32afc 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1395 +1,1397 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoID.h" #include #include #include #include #include #include #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN # include #endif 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())); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); 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", false).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); m_cmbKineticScrollingGesture->addItem(i18n("Disabled")); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar()); // // 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)); int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); m_chkCompressKra->setChecked(cfg.compressKra()); m_backupFileCheckBox->setChecked(cfg.backupFile()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); } void GeneralTab::setDefault() { KisConfig cfg(true); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); 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.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivity->value(); } bool GeneralTab::kineticScrollingScrollbar() { return m_chkKineticScrollingScrollbar->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } 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; SqueezedComboBox *cmb = new SqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); KisImageConfig cfgImage(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->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { KisConfig cfg(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); } #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); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } //--------------------------------------------------------------------------------------------------- #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))); 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)); 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()); 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" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); #ifdef Q_OS_WIN const QString rendererAngleText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); cmbRenderer->clear(); QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererAngle) { qtPreferredRendererText = rendererAngleText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbRenderer->setCurrentIndex(0); if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { cmbRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererAngle) { cmbRenderer->addItem(rendererAngleText, KisOpenGL::RendererAngle); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererAngle) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } #else lblRenderer->setEnabled(false); cmbRenderer->setEnabled(false); cmbRenderer->clear(); cmbRenderer->addItem(rendererOpenGLText); cmbRenderer->setCurrentIndex(0); #endif #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } 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(cfg.selectionOverlayMaskColor()); + c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); - sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF()); + 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); cmbRenderer->setCurrentIndex(0); #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(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); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(KPageDialog::Tree); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(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())); } KisDlgPreferences::~KisDlgPreferences() { } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg(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().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingScrollbar(dialog->m_general->kineticScrollingScrollbar()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(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.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); #ifdef Q_OS_WIN { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbRenderer->itemData( dialog->m_displaySettings->cmbRenderer->currentIndex()).toInt()); KisOpenGL::setNextUserOpenGLRendererConfig(renderer); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } #endif if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); - cfg.setSelectionOverlayMaskColor(c.toQColor()); + 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/kis_animation_frame_cache.cpp b/libs/ui/kis_animation_frame_cache.cpp index 6518d5159c..829cf3249e 100644 --- a/libs/ui/kis_animation_frame_cache.cpp +++ b/libs/ui/kis_animation_frame_cache.cpp @@ -1,414 +1,414 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_frame_cache.h" #include #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "KisPart.h" #include "kis_animation_cache_populator.h" #include #include "KisFrameCacheSwapper.h" #include "KisInMemoryFrameCacheSwapper.h" #include "kis_image_config.h" #include "kis_config_notifier.h" #include "opengl/kis_opengl_image_textures.h" #include #include struct KisAnimationFrameCache::Private { Private(KisOpenGLImageTexturesSP _textures) : textures(_textures) { image = textures->image(); } ~Private() { } KisOpenGLImageTexturesSP textures; KisImageWSP image; QScopedPointer swapper; int frameSizeLimit = 777; KisOpenGLUpdateInfoSP fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod); struct Frame { KisOpenGLUpdateInfoSP openGlFrame; int length; Frame(KisOpenGLUpdateInfoSP info, int length) : openGlFrame(info), length(length) {} }; QMap newFrames; int getFrameIdAtTime(int time) const { if (newFrames.isEmpty()) return -1; auto it = newFrames.upperBound(time); if (it != newFrames.constBegin()) it--; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != newFrames.constEnd(), 0); const int start = it.key(); const int length = it.value(); bool foundFrameValid = false; if (length == -1) { if (start <= time) { foundFrameValid = true; } } else { int end = start + length - 1; if (start <= time && time <= end) { foundFrameValid = true; } } return foundFrameValid ? start : -1; } bool hasFrame(int time) const { return getFrameIdAtTime(time) >= 0; } KisOpenGLUpdateInfoSP getFrame(int time) { const int frameId = getFrameIdAtTime(time); return frameId >= 0 ? swapper->loadFrame(frameId) : 0; } void addFrame(KisOpenGLUpdateInfoSP info, const KisTimeRange& range) { invalidate(range); const int length = range.isInfinite() ? -1 : range.end() - range.start() + 1; newFrames.insert(range.start(), length); swapper->saveFrame(range.start(), info, image->bounds()); } /** * Invalidate any cached frames within the given time range. * @param range * @return true if frames were invalidated, false if nothing was changed */ bool invalidate(const KisTimeRange& range) { if (newFrames.isEmpty()) return false; bool cacheChanged = false; auto it = newFrames.lowerBound(range.start()); if (it.key() != range.start() && it != newFrames.begin()) it--; while (it != newFrames.end()) { const int start = it.key(); const int length = it.value(); const bool frameIsInfinite = (length == -1); const int end = start + length - 1; if (start >= range.start()) { if (!range.isInfinite() && start > range.end()) { break; } if (!range.isInfinite() && (frameIsInfinite || end > range.end())) { // Reinsert with a later start int newStart = range.end() + 1; int newLength = frameIsInfinite ? -1 : (end - newStart + 1); newFrames.insert(newStart, newLength); swapper->moveFrame(start, newStart); } else { swapper->forgetFrame(start); } it = newFrames.erase(it); cacheChanged = true; continue; } else if (frameIsInfinite || end >= range.start()) { const int newEnd = range.start() - 1; *it = newEnd - start + 1; cacheChanged = true; } it++; } return cacheChanged; } int effectiveLevelOfDetail(const QRect &rc) const { if (!frameSizeLimit) return 0; const int maxDimension = KisAlgebra2D::maxDimension(rc); const qreal minLod = -std::log2(qreal(frameSizeLimit) / maxDimension); const int lodLimit = qMax(0, qCeil(minLod)); return lodLimit; } // TODO: verify that we don't have any leak here! typedef QMap CachesMap; static CachesMap caches; }; KisAnimationFrameCache::Private::CachesMap KisAnimationFrameCache::Private::caches; KisAnimationFrameCacheSP KisAnimationFrameCache::getFrameCache(KisOpenGLImageTexturesSP textures) { KisAnimationFrameCache *cache; Private::CachesMap::iterator it = Private::caches.find(textures); if (it == Private::caches.end()) { cache = new KisAnimationFrameCache(textures); Private::caches.insert(textures, cache); } else { cache = it.value(); } return cache; } const QList KisAnimationFrameCache::caches() { return Private::caches.values(); } KisAnimationFrameCache::KisAnimationFrameCache(KisOpenGLImageTexturesSP textures) : m_d(new Private(textures)) { // create swapping backend slotConfigChanged(); connect(m_d->image->animationInterface(), SIGNAL(sigFramesChanged(KisTimeRange,QRect)), this, SLOT(framesChanged(KisTimeRange,QRect))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); } KisAnimationFrameCache::~KisAnimationFrameCache() { Private::caches.remove(m_d->textures); } bool KisAnimationFrameCache::uploadFrame(int time) { KisOpenGLUpdateInfoSP info = m_d->getFrame(time); if (!info) { // Do nothing! // // Previously we were trying to start cache regeneration in this point, // but it caused even bigger slowdowns when scrubbing } else { m_d->textures->recalculateCache(info); } return bool(info); } bool KisAnimationFrameCache::shouldUploadNewFrame(int newTime, int oldTime) const { if (oldTime < 0) return true; const int oldKeyframeStart = m_d->getFrameIdAtTime(oldTime); if (oldKeyframeStart < 0) return true; const int oldKeyFrameLength = m_d->newFrames[oldKeyframeStart]; return !(newTime >= oldKeyframeStart && (newTime < oldKeyframeStart + oldKeyFrameLength || oldKeyFrameLength == -1)); } KisAnimationFrameCache::CacheStatus KisAnimationFrameCache::frameStatus(int time) const { return m_d->hasFrame(time) ? Cached : Uncached; } KisImageWSP KisAnimationFrameCache::image() { return m_d->image; } void KisAnimationFrameCache::framesChanged(const KisTimeRange &range, const QRect &rect) { Q_UNUSED(rect); if (!range.isValid()) return; bool cacheChanged = m_d->invalidate(range); if (cacheChanged) { emit changed(); } } void KisAnimationFrameCache::slotConfigChanged() { m_d->newFrames.clear(); KisImageConfig cfg(true); if (cfg.useOnDiskAnimationCacheSwapping()) { m_d->swapper.reset(new KisFrameCacheSwapper(m_d->textures->updateInfoBuilder(), cfg.swapDir())); } else { m_d->swapper.reset(new KisInMemoryFrameCacheSwapper()); } m_d->frameSizeLimit = cfg.useAnimationCacheFrameSizeLimit() ? cfg.animationCacheFrameSizeLimit() : 0; emit changed(); } KisOpenGLUpdateInfoSP KisAnimationFrameCache::Private::fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod) { if (lod > 0) { KisPaintDeviceSP tempDevice = new KisPaintDevice(image->projection()->colorSpace()); tempDevice->prepareClone(image->projection()); image->projection()->generateLodCloneDevice(tempDevice, image->projection()->extent(), lod); const QRect fetchRect = KisLodTransform::alignedRect(requestedRect, lod); return textures->updateInfoBuilder().buildUpdateInfo(fetchRect, tempDevice, image->bounds(), lod, true); } else { return textures->updateCache(requestedRect, image); } } KisOpenGLUpdateInfoSP KisAnimationFrameCache::fetchFrameData(int time, KisImageSP image, const QRegion &requestedRegion) const { if (time != image->animationInterface()->currentTime()) { qWarning() << "WARNING: KisAnimationFrameCache::frameReady image's time doesn't coincide with the requested time!"; qWarning() << " " << ppVar(image->animationInterface()->currentTime()) << ppVar(time); } // the frames are always generated at full scale KIS_SAFE_ASSERT_RECOVER_NOOP(image->currentLevelOfDetail() == 0); const int lod = m_d->effectiveLevelOfDetail(requestedRegion.boundingRect()); KisOpenGLUpdateInfoSP totalInfo; Q_FOREACH (const QRect &rc, requestedRegion.rects()) { KisOpenGLUpdateInfoSP info = m_d->fetchFrameDataImpl(image, rc, lod); if (!totalInfo) { totalInfo = info; } else { const bool result = totalInfo->tryMergeWith(*info); KIS_SAFE_ASSERT_RECOVER_NOOP(result); } } return totalInfo; } void KisAnimationFrameCache::addConvertedFrameData(KisOpenGLUpdateInfoSP info, int time) { - KisTimeRange identicalRange = KisTimeRange::infinite(0); - KisTimeRange::calculateTimeRangeRecursive(m_d->image->root(), time, identicalRange, true); + const KisTimeRange identicalRange = + KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), time); m_d->addFrame(info, identicalRange); emit changed(); } void KisAnimationFrameCache::dropLowQualityFrames(const KisTimeRange &range, const QRect ®ionOfInterest, const QRect &minimalRect) { KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite()); if (m_d->newFrames.isEmpty()) return; auto it = m_d->newFrames.upperBound(range.start()); // the vector is guaranteed to be non-empty, // so decrementing iterator is safe if (it != m_d->newFrames.begin()) it--; while (it != m_d->newFrames.end() && it.key() <= range.end()) { const int frameId = it.key(); const int frameLength = it.value(); if (frameId + frameLength - 1 < range.start()) { ++it; continue; } const QRect frameRect = m_d->swapper->frameDirtyRect(frameId); const int frameLod = m_d->swapper->frameLevelOfDetail(frameId); if (frameLod > m_d->effectiveLevelOfDetail(regionOfInterest) || !frameRect.contains(minimalRect)) { m_d->swapper->forgetFrame(frameId); it = m_d->newFrames.erase(it); } else { ++it; } } } bool KisAnimationFrameCache::framesHaveValidRoi(const KisTimeRange &range, const QRect ®ionOfInterest) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!range.isInfinite(), false); if (m_d->newFrames.isEmpty()) return false; auto it = m_d->newFrames.upperBound(range.start()); if (it != m_d->newFrames.begin()) it--; int expectedNextFrameStart = it.key(); while (it.key() <= range.end()) { const int frameId = it.key(); const int frameLength = it.value(); if (frameId + frameLength - 1 < range.start()) { expectedNextFrameStart = frameId + frameLength; ++it; continue; } if (expectedNextFrameStart != frameId) { KIS_SAFE_ASSERT_RECOVER_NOOP(expectedNextFrameStart < frameId); return false; } if (!m_d->swapper->frameDirtyRect(frameId).contains(regionOfInterest)) { return false; } expectedNextFrameStart = frameId + frameLength; ++it; } return true; } diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 521429e481..9342d33d7c 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,1998 +1,1987 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const { return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0); } void KisConfig::setPreferredVectorImportResolutionPPI(int value) const { m_cfg.writeEntry("preferredVectorImportResolution", value); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col)); } void KisConfig::setMDIBackgroundColor(const QColor &v) const { m_cfg.writeEntry("mdiBackgroundColor", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg(true); QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString cs = canvasState(); #ifdef Q_OS_WIN return (m_cfg.readEntry("useOpenGLWindows", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #else return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); #endif } void KisConfig::setUseOpenGL(bool useOpenGL) const { #ifdef Q_OS_WIN m_cfg.writeEntry("useOpenGLWindows", useOpenGL); #else m_cfg.writeEntry("useOpenGL", useOpenGL); #endif } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } -QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const -{ - QColor def(255, 0, 0, 220); - return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def)); -} - -void KisConfig::setSelectionOverlayMaskColor(const QColor &color) -{ - m_cfg.writeEntry("selectionOverlayMaskColor", color); -} - bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) const { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { m_cfg.writeEntry("useWin8PointerInput", value); } #else Q_UNUSED(value) #endif } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } QString KisConfig::ocioConfigurationPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString())); } void KisConfig::setOcioConfigurationPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path); } QString KisConfig::ocioLutPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString())); } void KisConfig::setOcioLutPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("KineticScrollingGesture", 0)); } void KisConfig::setKineticScrollingGesture(int gesture) { m_cfg.writeEntry("KineticScrollingGesture", gesture); } int KisConfig::kineticScrollingSensitivity(bool defaultValue) const { return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); } void KisConfig::setKineticScrollingSensitivity(int sensitivity) { m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); } bool KisConfig::kineticScrollingScrollbar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingScrollbar", true)); } void KisConfig::setKineticScrollingScrollbar(bool scrollbar) { m_cfg.writeEntry("KineticScrollingScrollbar", scrollbar); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } QString KisConfig::customFFMpegPath(bool defaultValue) const { return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString()); } void KisConfig::setCustomFFMpegPath(const QString &value) const { m_cfg.writeEntry("ffmpegExecutablePath", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } QColor KisConfig::defaultAssistantsColor(bool defaultValue) const { static const QColor defaultColor = QColor(176, 176, 176, 255); return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor); } void KisConfig::setDefaultAssistantsColor(const QColor &color) const { m_cfg.writeEntry("defaultAssistantsColor", color); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h index 60343daef0..89c02226d8 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,601 +1,598 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CONFIG_H_ #define KIS_CONFIG_H_ #include #include #include #include #include #include #include #include #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class KRITAUI_EXPORT KisConfig { public: /** * @brief KisConfig create a kisconfig object * @param readOnly if true, there will be no call to sync when the object is deleted. * Any KisConfig object created in a thread must be read-only. */ KisConfig(bool readOnly); ~KisConfig(); bool disableTouchOnCanvas(bool defaultValue = false) const; void setDisableTouchOnCanvas(bool value) const; bool useProjections(bool defaultValue = false) const; void setUseProjections(bool useProj) const; bool undoEnabled(bool defaultValue = false) const; void setUndoEnabled(bool undo) const; int undoStackLimit(bool defaultValue = false) const; void setUndoStackLimit(int limit) const; bool useCumulativeUndoRedo(bool defaultValue = false) const; void setCumulativeUndoRedo(bool value); double stackT1(bool defaultValue = false) const; void setStackT1(int T1); double stackT2(bool defaultValue = false) const; void setStackT2(int T2); int stackN(bool defaultValue = false) const; void setStackN(int N); qint32 defImageWidth(bool defaultValue = false) const; void defImageWidth(qint32 width) const; qint32 defImageHeight(bool defaultValue = false) const; void defImageHeight(qint32 height) const; qreal defImageResolution(bool defaultValue = false) const; void defImageResolution(qreal res) const; int preferredVectorImportResolutionPPI(bool defaultValue = false) const; void setPreferredVectorImportResolutionPPI(int value) const; /** * @return the id of the default color model used for creating new images. */ QString defColorModel(bool defaultValue = false) const; /** * set the id of the default color model used for creating new images. */ void defColorModel(const QString & model) const; /** * @return the id of the default color depth used for creating new images. */ QString defaultColorDepth(bool defaultValue = false) const; /** * set the id of the default color depth used for creating new images. */ void setDefaultColorDepth(const QString & depth) const; /** * @return the id of the default color profile used for creating new images. */ QString defColorProfile(bool defaultValue = false) const; /** * set the id of the default color profile used for creating new images. */ void defColorProfile(const QString & depth) const; CursorStyle newCursorStyle(bool defaultValue = false) const; void setNewCursorStyle(CursorStyle style); QColor getCursorMainColor(bool defaultValue = false) const; void setCursorMainColor(const QColor& v) const; OutlineStyle newOutlineStyle(bool defaultValue = false) const; void setNewOutlineStyle(OutlineStyle style); QRect colorPreviewRect() const; void setColorPreviewRect(const QRect &rect); /// get the profile the user has selected for the given screen QString monitorProfile(int screen) const; void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const; QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const; void setMonitorForScreen(int screen, const QString& monitor); /// Get the actual profile to be used for the given screen, which is /// either the screen profile set by the color management system or /// the custom monitor profile set by the user, depending on the configuration const KoColorProfile *displayProfile(int screen) const; QString workingColorSpace(bool defaultValue = false) const; void setWorkingColorSpace(const QString & workingColorSpace) const; QString importProfile(bool defaultValue = false) const; void setImportProfile(const QString & importProfile) const; QString printerColorSpace(bool defaultValue = false) const; void setPrinterColorSpace(const QString & printerColorSpace) const; QString printerProfile(bool defaultValue = false) const; void setPrinterProfile(const QString & printerProfile) const; bool useBlackPointCompensation(bool defaultValue = false) const; void setUseBlackPointCompensation(bool useBlackPointCompensation) const; bool allowLCMSOptimization(bool defaultValue = false) const; void setAllowLCMSOptimization(bool allowLCMSOptimization); void writeKoColor(const QString& name, const KoColor& color) const; KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const; bool showRulers(bool defaultValue = false) const; void setShowRulers(bool rulers) const; bool forceShowSaveMessages(bool defaultValue = true) const; void setForceShowSaveMessages(bool value) const; bool forceShowAutosaveMessages(bool defaultValue = true) const; void setForceShowAutosaveMessages(bool ShowAutosaveMessages) const; bool rulersTrackMouse(bool defaultValue = false) const; void setRulersTrackMouse(bool value) const; qint32 pasteBehaviour(bool defaultValue = false) const; void setPasteBehaviour(qint32 behaviour) const; qint32 monitorRenderIntent(bool defaultValue = false) const; void setRenderIntent(qint32 monitorRenderIntent) const; bool useOpenGL(bool defaultValue = false) const; void setUseOpenGL(bool useOpenGL) const; int openGLFilteringMode(bool defaultValue = false) const; void setOpenGLFilteringMode(int filteringMode); bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); bool showAdvancedOpenGLSettings(bool defaultValue = false) const; bool forceOpenGLFenceWorkaround(bool defaultValue = false) const; int numMipmapLevels(bool defaultValue = false) const; int openGLTextureSize(bool defaultValue = false) const; int textureOverlapBorder() const; quint32 getGridMainStyle(bool defaultValue = false) const; void setGridMainStyle(quint32 v) const; quint32 getGridSubdivisionStyle(bool defaultValue = false) const; void setGridSubdivisionStyle(quint32 v) const; QColor getGridMainColor(bool defaultValue = false) const; void setGridMainColor(const QColor & v) const; QColor getGridSubdivisionColor(bool defaultValue = false) const; void setGridSubdivisionColor(const QColor & v) const; QColor getPixelGridColor(bool defaultValue = false) const; void setPixelGridColor(const QColor & v) const; qreal getPixelGridDrawingThreshold(bool defaultValue = false) const; void setPixelGridDrawingThreshold(qreal v) const; bool pixelGridEnabled(bool defaultValue = false) const; void enablePixelGrid(bool v) const; quint32 guidesLineStyle(bool defaultValue = false) const; void setGuidesLineStyle(quint32 v) const; QColor guidesColor(bool defaultValue = false) const; void setGuidesColor(const QColor & v) const; void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const; void saveSnapConfig(const KisSnapConfig &config); qint32 checkSize(bool defaultValue = false) const; void setCheckSize(qint32 checkSize) const; bool scrollCheckers(bool defaultValue = false) const; void setScrollingCheckers(bool scollCheckers) const; QColor checkersColor1(bool defaultValue = false) const; void setCheckersColor1(const QColor & v) const; QColor checkersColor2(bool defaultValue = false) const; void setCheckersColor2(const QColor & v) const; QColor canvasBorderColor(bool defaultValue = false) const; void setCanvasBorderColor(const QColor &color) const; bool hideScrollbars(bool defaultValue = false) const; void setHideScrollbars(bool value) const; bool antialiasCurves(bool defaultValue = false) const; void setAntialiasCurves(bool v) const; - QColor selectionOverlayMaskColor(bool defaultValue = false) const; - void setSelectionOverlayMaskColor(const QColor &color); - bool antialiasSelectionOutline(bool defaultValue = false) const; void setAntialiasSelectionOutline(bool v) const; bool showRootLayer(bool defaultValue = false) const; void setShowRootLayer(bool showRootLayer) const; bool showGlobalSelection(bool defaultValue = false) const; void setShowGlobalSelection(bool showGlobalSelection) const; bool showOutlineWhilePainting(bool defaultValue = false) const; void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const; bool forceAlwaysFullSizedOutline(bool defaultValue = false) const; void setForceAlwaysFullSizedOutline(bool value) const; enum SessionOnStartup { SOS_BlankSession, SOS_PreviousSession, SOS_ShowSessionManager }; SessionOnStartup sessionOnStartup(bool defaultValue = false) const; void setSessionOnStartup(SessionOnStartup value); bool saveSessionOnQuit(bool defaultValue) const; void setSaveSessionOnQuit(bool value); qreal outlineSizeMinimum(bool defaultValue = false) const; void setOutlineSizeMinimum(qreal outlineSizeMinimum) const; qreal selectionViewSizeMinimum(bool defaultValue = false) const; void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const; int autoSaveInterval(bool defaultValue = false) const; void setAutoSaveInterval(int seconds) const; bool backupFile(bool defaultValue = false) const; void setBackupFile(bool backupFile) const; bool showFilterGallery(bool defaultValue = false) const; void setShowFilterGallery(bool showFilterGallery) const; bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const; void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const; // OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED QString canvasState(bool defaultValue = false) const; void setCanvasState(const QString& state) const; bool toolOptionsPopupDetached(bool defaultValue = false) const; void setToolOptionsPopupDetached(bool detached) const; bool paintopPopupDetached(bool defaultValue = false) const; void setPaintopPopupDetached(bool detached) const; QString pressureTabletCurve(bool defaultValue = false) const; void setPressureTabletCurve(const QString& curveString) const; bool useWin8PointerInput(bool defaultValue = false) const; void setUseWin8PointerInput(bool value) const; qreal vastScrolling(bool defaultValue = false) const; void setVastScrolling(const qreal factor) const; int presetChooserViewMode(bool defaultValue = false) const; void setPresetChooserViewMode(const int mode) const; int presetIconSize(bool defaultValue = false) const; void setPresetIconSize(const int value) const; bool firstRun(bool defaultValue = false) const; void setFirstRun(const bool firstRun) const; bool clicklessSpacePan(bool defaultValue = false) const; void setClicklessSpacePan(const bool toggle) const; int horizontalSplitLines(bool defaultValue = false) const; void setHorizontalSplitLines(const int numberLines) const; int verticalSplitLines(bool defaultValue = false) const; void setVerticalSplitLines(const int numberLines) const; bool hideDockersFullscreen(bool defaultValue = false) const; void setHideDockersFullscreen(const bool value) const; bool showDockers(bool defaultValue = false) const; void setShowDockers(const bool value) const; bool showStatusBar(bool defaultValue = false) const; void setShowStatusBar(const bool value) const; bool hideMenuFullscreen(bool defaultValue = false) const; void setHideMenuFullscreen(const bool value) const; bool hideScrollbarsFullscreen(bool defaultValue = false) const; void setHideScrollbarsFullscreen(const bool value) const; bool hideStatusbarFullscreen(bool defaultValue = false) const; void setHideStatusbarFullscreen(const bool value) const; bool hideTitlebarFullscreen(bool defaultValue = false) const; void setHideTitlebarFullscreen(const bool value) const; bool hideToolbarFullscreen(bool defaultValue = false) const; void setHideToolbarFullscreen(const bool value) const; bool fullscreenMode(bool defaultValue = false) const; void setFullscreenMode(const bool value) const; QStringList favoriteCompositeOps(bool defaultValue = false) const; void setFavoriteCompositeOps(const QStringList& compositeOps) const; QString exportConfiguration(const QString &filterId, bool defaultValue = false) const; void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; QString importConfiguration(const QString &filterId, bool defaultValue = false) const; void setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; bool useOcio(bool defaultValue = false) const; void setUseOcio(bool useOCIO) const; int favoritePresets(bool defaultValue = false) const; void setFavoritePresets(const int value); bool levelOfDetailEnabled(bool defaultValue = false) const; void setLevelOfDetailEnabled(bool value); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) const; QString ocioConfigurationPath(bool defaultValue = false) const; void setOcioConfigurationPath(const QString &path) const; QString ocioLutPath(bool defaultValue = false) const; void setOcioLutPath(const QString &path) const; int ocioLutEdgeSize(bool defaultValue = false) const; void setOcioLutEdgeSize(int value); bool ocioLockColorVisualRepresentation(bool defaultValue = false) const; void setOcioLockColorVisualRepresentation(bool value); bool useSystemMonitorProfile(bool defaultValue = false) const; void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const; QString defaultPalette(bool defaultValue = false) const; void setDefaultPalette(const QString& name) const; QString toolbarSlider(int sliderNumber, bool defaultValue = false) const; void setToolbarSlider(int sliderNumber, const QString &slider); bool sliderLabels(bool defaultValue = false) const; void setSliderLabels(bool enabled); QString currentInputProfile(bool defaultValue = false) const; void setCurrentInputProfile(const QString& name); bool presetStripVisible(bool defaultValue = false) const; void setPresetStripVisible(bool visible); bool scratchpadVisible(bool defaultValue = false) const; void setScratchpadVisible(bool visible); bool showSingleChannelAsColor(bool defaultValue = false) const; void setShowSingleChannelAsColor(bool asColor); bool hidePopups(bool defaultValue = false) const; void setHidePopups(bool hidepopups); int numDefaultLayers(bool defaultValue = false) const; void setNumDefaultLayers(int num); quint8 defaultBackgroundOpacity(bool defaultValue = false) const; void setDefaultBackgroundOpacity(quint8 value); QColor defaultBackgroundColor(bool defaultValue = false) const; void setDefaultBackgroundColor(QColor value); enum BackgroundStyle { LAYER = 0, PROJECTION = 1 }; BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const; void setDefaultBackgroundStyle(BackgroundStyle value); int lineSmoothingType(bool defaultValue = false) const; void setLineSmoothingType(int value); qreal lineSmoothingDistance(bool defaultValue = false) const; void setLineSmoothingDistance(qreal value); qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const; void setLineSmoothingTailAggressiveness(qreal value); bool lineSmoothingSmoothPressure(bool defaultValue = false) const; void setLineSmoothingSmoothPressure(bool value); bool lineSmoothingScalableDistance(bool defaultValue = false) const; void setLineSmoothingScalableDistance(bool value); qreal lineSmoothingDelayDistance(bool defaultValue = false) const; void setLineSmoothingDelayDistance(qreal value); bool lineSmoothingUseDelayDistance(bool defaultValue = false) const; void setLineSmoothingUseDelayDistance(bool value); bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const; void setLineSmoothingFinishStabilizedCurve(bool value); bool lineSmoothingStabilizeSensors(bool defaultValue = false) const; void setLineSmoothingStabilizeSensors(bool value); int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); bool trackTabletEventLatency(bool defaultValue = false) const; void setTrackTabletEventLatency(bool value); bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); bool shouldEatDriverShortcuts(bool defaultValue = false) const; bool testingCompressBrushEvents(bool defaultValue = false) const; void setTestingCompressBrushEvents(bool value); const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const; void setCustomColorSelectorColorSpace(const KoColorSpace *cs); bool useDirtyPresets(bool defaultValue = false) const; void setUseDirtyPresets(bool value); bool useEraserBrushSize(bool defaultValue = false) const; void setUseEraserBrushSize(bool value); bool useEraserBrushOpacity(bool defaultValue = false) const; void setUseEraserBrushOpacity(bool value); QColor getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QColor & v) const; QString getMDIBackgroundImage(bool defaultValue = false) const; void setMDIBackgroundImage(const QString & fileName) const; int workaroundX11SmoothPressureSteps(bool defaultValue = false) const; bool showCanvasMessages(bool defaultValue = false) const; void setShowCanvasMessages(bool show); bool compressKra(bool defaultValue = false) const; void setCompressKra(bool compress); bool toolOptionsInDocker(bool defaultValue = false) const; void setToolOptionsInDocker(bool inDocker); int kineticScrollingGesture(bool defaultValue = false) const; void setKineticScrollingGesture(int kineticScroll); int kineticScrollingSensitivity(bool defaultValue = false) const; void setKineticScrollingSensitivity(int sensitivity); bool kineticScrollingScrollbar(bool defaultValue = false) const; void setKineticScrollingScrollbar(bool scrollbar); void setEnableOpenGLFramerateLogging(bool value) const; bool enableOpenGLFramerateLogging(bool defaultValue = false) const; void setEnableBrushSpeedLogging(bool value) const; bool enableBrushSpeedLogging(bool defaultValue = false) const; void setEnableAmdVectorizationWorkaround(bool value); bool enableAmdVectorizationWorkaround(bool defaultValue = false) const; bool animationDropFrames(bool defaultValue = false) const; void setAnimationDropFrames(bool value); int scrubbingUpdatesDelay(bool defaultValue = false) const; void setScrubbingUpdatesDelay(int value); int scrubbingAudioUpdatesDelay(bool defaultValue = false) const; void setScrubbingAudioUpdatesDelay(int value); int audioOffsetTolerance(bool defaultValue = false) const; void setAudioOffsetTolerance(int value); bool switchSelectionCtrlAlt(bool defaultValue = false) const; void setSwitchSelectionCtrlAlt(bool value); bool convertToImageColorspaceOnImport(bool defaultValue = false) const; void setConvertToImageColorspaceOnImport(bool value); int stabilizerSampleSize(bool defaultValue = false) const; void setStabilizerSampleSize(int value); bool stabilizerDelayedPaint(bool defaultValue = false) const; void setStabilizerDelayedPaint(bool value); QString customFFMpegPath(bool defaultValue = false) const; void setCustomFFMpegPath(const QString &value) const; bool showBrushHud(bool defaultValue = false) const; void setShowBrushHud(bool value); QString brushHudSetting(bool defaultValue = false) const; void setBrushHudSetting(const QString &value) const; bool calculateAnimationCacheInBackground(bool defaultValue = false) const; void setCalculateAnimationCacheInBackground(bool value); QColor defaultAssistantsColor(bool defaultValue = false) const; void setDefaultAssistantsColor(const QColor &color) const; template void writeEntry(const QString& name, const T& value) { m_cfg.writeEntry(name, value); } template void writeList(const QString& name, const QList& value) { m_cfg.writeEntry(name, value); } template T readEntry(const QString& name, const T& defaultValue=T()) { return m_cfg.readEntry(name, defaultValue); } template QList readList(const QString& name, const QList& defaultValue=QList()) { return m_cfg.readEntry(name, defaultValue); } /// get the profile the color management system has stored for the given screen static const KoColorProfile* getScreenProfile(int screen); private: KisConfig(const KisConfig&); KisConfig& operator=(const KisConfig&) const; private: mutable KConfigGroup m_cfg; bool m_readOnly; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index c3ceb8d626..e3ba49eec4 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1506 +1,1505 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "kis_shape_layer.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" #include struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; KisAction *showInTimeline; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); //if (!shape) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); //if (!shapeLayer) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } - } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("save_vector_node_to_svg"); connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage())); action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); action = actionManager->createAction("show_in_timeline"); action->setCheckable(true); connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool))); m_d->showInTimeline = action; NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(createNode(const QString &))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); CONVERT_NODE_ACTION_2("convert_layer_to_file_layer", "KisFileLayer", QStringList()<< "KisFileLayer" << "KisCloneLayer"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(convertNode(const QString &))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("toggle_layer_visibility"); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); action = actionManager->createAction("toggle_layer_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); action = actionManager->createAction("toggle_layer_inherit_alpha"); connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); action = actionManager->createAction("toggle_layer_alpha_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryRestartIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); if (m && m->active() && l && l->selectionMask()) { l->selectionMask()->setActive(false); } } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); if (checked && activeNode) { // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryRestartIsolatedMode() { KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode) return; this->toggleIsolateMode(true); } void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN(activeNode); // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { m_d->layerManager.addLayer(activeNode); } else if (nodeType == "KisGroupLayer") { m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { m_d->layerManager.addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { m_d->layerManager.addFileLayer(activeNode); } } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } void KisNodeManager::slotShowHideTimeline(bool value) { Q_FOREACH (KisNodeSP node, selectedNodes()) { node->setUseInTimeline(value); } } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } return m_d->layerManager.addLayer(activeNode); } void KisNodeManager::convertNode(const QString &nodeType) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, true); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true); } m_d->commandsAdapter.removeNode(activeNode); m_d->commandsAdapter.endMacro(); } else if (nodeType == "KisFileLayer") { m_d->layerManager.convertLayerToFileLayer(activeNode); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; if (node->inherits("KisShapeLayer")) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); { KisSignalsBlocker b(m_d->showInTimeline); m_d->showInTimeline->setChecked(node->useInTimeline()); } } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if ((selectedNodes().size() > 1 && node->inherits("KisLayer")) || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange) { if (!node) return; if (node->opacity() == opacity) return; if (!finalChange) { node->setOpacity(opacity); node->setDirty(); } else { m_d->commandsAdapter.setOpacity(node, opacity); } } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden) { if (dynamic_cast(node.data())) { return true; } if (isGlobalSelectionHidden && dynamic_cast(node.data()) && (!node->parent() || !node->parent()->parent())) { return true; } return false; } void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity), finalChange); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && (nodeJuggler->isEnded() || !nodeJuggler->canMergeAction(actionName)))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } void KisNodeManager::rotate(double radians) { if(!m_d->view->image()) return; KisNodeSP node = activeNode(); if (!node) return; if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; m_d->view->image()->rotateNode(node, radians); } void KisNodeManager::rotate180() { rotate(M_PI); } void KisNodeManager::rotateLeft90() { rotate(-M_PI / 2); } void KisNodeManager::rotateRight90() { rotate(M_PI / 2); } void KisNodeManager::shear(double angleX, double angleY) { if (!m_d->view->image()) return; KisNodeSP node = activeNode(); if (!node) return; if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; m_d->view->image()->shearNode(node, angleX, angleY); } void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy) { KisNodeSP node = activeNode(); KIS_ASSERT_RECOVER_RETURN(node); m_d->view->image()->scaleNode(node, sx, sy, filterStrategy); nodesUpdated(); } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->exportDocumentSync(url, mimefilter.toLatin1()); } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); KisPaintDeviceSP device = node->paintDevice(); if (!device) { device = node->projection(); } m_d->saveDeviceAsImage(device, node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } #include "SvgWriter.h" void KisNodeManager::saveVectorLayerAsImage() { KisShapeLayerSP shapeLayer = qobject_cast(activeNode().data()); if (!shapeLayer) { return; } KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18nc("@title:window", "Export to SVG")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; const QSizeF sizeInPx = m_d->view->image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(), sizeInPx.height() / m_d->view->image()->yRes()); QList shapes = shapeLayer->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); if (!writer.save(filename, sizeInPt, true)) { QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1").arg(filename)); } } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.").arg(parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::toggleLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isLocked = active->userLocked(); for (auto &node : nodes) { node->setUserLocked(!isLocked); } } void KisNodeManager::toggleVisibility() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isVisible = active->visible(); for (auto &node : nodes) { node->setVisible(!isVisible); node->setDirty(); } } void KisNodeManager::toggleAlphaLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaLocked = layer->alphaLocked(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->setAlphaLocked(!isAlphaLocked); } } } void KisNodeManager::toggleInheritAlpha() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaDisabled = layer->alphaChannelDisabled(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->disableAlphaChannel(!isAlphaDisabled); node->setDirty(); } } } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_selection_decoration.cc b/libs/ui/kis_selection_decoration.cc index b4ce1c5806..db7d67aa47 100644 --- a/libs/ui/kis_selection_decoration.cc +++ b/libs/ui/kis_selection_decoration.cc @@ -1,208 +1,225 @@ /* * Copyright (c) 2008 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_decoration.h" #include #include #include #include #include "kis_types.h" #include "KisViewManager.h" #include "kis_selection.h" #include "kis_image.h" #include "flake/kis_shape_selection.h" #include "kis_pixel_selection.h" #include "kis_update_outline_job.h" #include "kis_selection_manager.h" #include "canvas/kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_coordinates_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" +#include "kis_image_config.h" +#include "KisImageConfigNotifier.h" #include "kis_painting_tweaks.h" #include "KisView.h" +#include "kis_selection_mask.h" +#include static const unsigned int ANT_LENGTH = 4; static const unsigned int ANT_SPACE = 4; static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE; KisSelectionDecoration::KisSelectionDecoration(QPointerview) : KisCanvasDecoration("selection", view), m_signalCompressor(500 /*ms*/, KisSignalCompressor::FIRST_INACTIVE), m_offset(0), m_mode(Ants) { KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen, ANT_LENGTH, ANT_SPACE); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); + connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); m_antsTimer = new QTimer(this); m_antsTimer->setInterval(150); m_antsTimer->setSingleShot(false); connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent())); connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection())); } KisSelectionDecoration::~KisSelectionDecoration() { } KisSelectionDecoration::Mode KisSelectionDecoration::mode() const { return m_mode; } void KisSelectionDecoration::setMode(Mode mode) { m_mode = mode; selectionChanged(); } bool KisSelectionDecoration::selectionIsActive() { KisImageWSP image = view()->image(); Q_ASSERT(image); Q_UNUSED(image); KisSelectionSP selection = view()->selection(); return visible() && selection && (selection->hasPixelSelection() || selection->hasShapeSelection()) && selection->isVisible(); } void KisSelectionDecoration::selectionChanged() { + KisSelectionMaskSP mask = qobject_cast(view()->currentNode().data()); + if (!mask || !mask->active() || !mask->visible(true)) { + mask = 0; + } + + if (!view()->isCurrent() || + view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) { + + view()->image()->setOverlaySelectionMask(mask); + } + KisSelectionSP selection = view()->selection(); - if (selection && selectionIsActive()) { + if (!mask && selection && selectionIsActive()) { if ((m_mode == Ants && selection->outlineCacheValid()) || (m_mode == Mask && selection->thumbnailImageValid())) { m_signalCompressor.stop(); if (m_mode == Ants) { m_outlinePath = selection->outlineCache(); m_antsTimer->start(); } else { m_thumbnailImage = selection->thumbnailImage(); m_thumbnailImageTransform = selection->thumbnailImageTransform(); } if (view() && view()->canvasBase()) { view()->canvasBase()->updateCanvas(); } } else { m_signalCompressor.start(); } } else { m_signalCompressor.stop(); m_outlinePath = QPainterPath(); m_thumbnailImage = QImage(); m_thumbnailImageTransform = QTransform(); view()->canvasBase()->updateCanvas(); m_antsTimer->stop(); } } void KisSelectionDecoration::slotStartUpdateSelection() { KisSelectionSP selection = view()->selection(); if (!selection) return; view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor)); } void KisSelectionDecoration::slotConfigChanged() { + KisImageConfig imageConfig(true); KisConfig cfg(true); - m_maskColor = cfg.selectionOverlayMaskColor(); + m_maskColor = imageConfig.selectionOverlayMaskColor(); m_antialiasSelectionOutline = cfg.antialiasSelectionOutline(); } void KisSelectionDecoration::antsAttackEvent() { KisSelectionSP selection = view()->selection(); if (!selection) return; if (selectionIsActive()) { m_offset = (m_offset + 1) % ANT_ADVANCE_WIDTH; m_antsPen.setDashOffset(m_offset); view()->canvasBase()->updateCanvas(); } } void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) { Q_UNUSED(updateRect); Q_UNUSED(canvas); if (!selectionIsActive()) return; if ((m_mode == Ants && m_outlinePath.isEmpty()) || (m_mode == Mask && m_thumbnailImage.isNull())) return; QTransform transform = converter->imageToWidgetTransform(); gc.save(); gc.setTransform(transform, false); if (m_mode == Mask) { gc.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing, false); gc.setTransform(m_thumbnailImageTransform, true); gc.drawImage(QPoint(), m_thumbnailImage); QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds()); QRect r2 = m_thumbnailImage.rect(); QPainterPath p1; p1.addRect(r1); QPainterPath p2; p2.addRect(r2); gc.setBrush(m_maskColor); gc.setPen(Qt::NoPen); gc.drawPath(p1 - p2); } else /* if (m_mode == Ants) */ { gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, m_antialiasSelectionOutline); // render selection outline in white gc.setPen(m_outlinePen); gc.drawPath(m_outlinePath); // render marching ants in black (above the white outline) gc.setPen(m_antsPen); gc.drawPath(m_outlinePath); } gc.restore(); } void KisSelectionDecoration::setVisible(bool v) { KisCanvasDecoration::setVisible(v); selectionChanged(); } diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index c6ed2dcc93..554c0102c3 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,189 +1,184 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) #add_subdirectory(scratchpad) include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_derived_resources_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp - NAME_PREFIX "krita-ui-" - LINK_LIBRARIES kritaui Qt5::Test -) - -ecm_add_tests( kis_file_layer_test.cpp kis_multinode_property_test.cpp NAME_PREFIX "krita-ui-" - LINK_LIBRARIES kritaui kritaimage Qt5::Test + LINK_LIBRARIES kritaui Qt5::Test ) ecm_add_test( KisFrameSerializerTest.cpp TEST_NAME krita-ui-KisFrameSerializerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( KisFrameCacheStoreTest.cpp TEST_NAME krita-ui-KisFrameCacheStoreTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-KisSelectionDecorationTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeDummiesGraphTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeShapesGraphTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisModelIndexConverterTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME krita-ui-KisCategorizedListModelTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp - TEST_NAME krita-image-BaseNodeTest + TEST_NAME krita-ui-KisNodeJugglerCompressedTest LINK_LIBRARIES kritaimage kritaui Qt5::Test) ecm_add_test( kis_animation_exporter_test.cpp TEST_NAME kritaui-animation_exporter_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) set(kis_node_view_test_SRCS kis_node_view_test.cpp ../../../sdk/tests/testutil.cpp) qt5_add_resources(kis_node_view_test_SRCS ${krita_QRCS}) ecm_add_test(${kis_node_view_test_SRCS} TEST_NAME krita-image-kis_node_view_test LINK_LIBRARIES kritaimage kritaui Qt5::Test) ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME krita-ui-kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test) krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME krita-ui-kis_shape_controller_test LINK_LIBRARIES kritaimage kritaui Qt5::Test) krita_add_broken_unit_test( kis_prescaled_projection_test.cpp TEST_NAME krita-ui-kis_prescaled_projection_test LINK_LIBRARIES kritaui Qt5::Test) krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME krita-ui-KisExiv2Test LINK_LIBRARIES kritaimage kritaui Qt5::Test) krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME krita-ui-KisClipboardTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FreehandStrokeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FreehandStrokeBenchmark LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-KisPaintOnTransparencyMaskTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FillProcessingVisitorTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FilterStrokeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME krita-ui-KisSelectionManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) #set_tests_properties(krita-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME krita-ui-KisNodeManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisDummiesFacadeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisZoomAndPanTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) #set_tests_properties(krita-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME krita-ui-KisActionManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME krita-ui-KisCategoriesMapperTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_asl_layer_style_serializer_test.cpp TEST_NAME krita-ui-KisAslLayerStyleSerializerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_animation_importer_test.cpp - TEST_NAME kritaui-animation_importer_test + TEST_NAME krita-ui-animation_importer_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp - TEST_NAME kritaui-animation_frame_cache_test + TEST_NAME krita-ui-animation_frame_cache_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( ResourceBundleTest.cpp - TEST_NAME krita-resourcemanager-ResourceBundleTest + TEST_NAME krita-ui-ResourceBundleTest LINK_LIBRARIES kritaui kritalibbrush kritalibpaintop Qt5::Test) # FIXME this test doesn't compile -#ecm_add_test( -# kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp -# TEST_NAME krita-ui-KisInputManagerTest -# LINK_LIBRARIES kritaui kritaimage Qt5::Test) +ecm_add_test( + kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp + TEST_NAME krita-ui-KisInputManagerTest + LINK_LIBRARIES kritaui kritaimage Qt5::Test) diff --git a/plugins/impex/libkra/tests/data/load_test.kra b/libs/ui/tests/data/load_test.kra similarity index 100% copy from plugins/impex/libkra/tests/data/load_test.kra copy to libs/ui/tests/data/load_test.kra diff --git a/libs/ui/tests/kis_action_manager_test.cpp b/libs/ui/tests/kis_action_manager_test.cpp index 3be08dc64f..dd85665e5e 100644 --- a/libs/ui/tests/kis_action_manager_test.cpp +++ b/libs/ui/tests/kis_action_manager_test.cpp @@ -1,136 +1,136 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_action_manager_test.h" #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" - +#include void KisActionManagerTest::testUpdateGUI() { KisDocument* doc = createEmptyDocument(); KisMainWindow* mainWindow = KisPart::instance()->createMainWindow(); QPointer view = new KisView(doc, mainWindow->resourceManager(), mainWindow->actionCollection(), mainWindow); KisViewManager *viewManager = new KisViewManager(mainWindow, mainWindow->actionCollection()); KisPart::instance()->addView(view); mainWindow->showView(view); view->setViewManager(viewManager); viewManager->setCurrentView(view); KisAction* action = new KisAction("dummy", this); action->setActivationFlags(KisAction::ACTIVE_DEVICE); view->viewManager()->actionManager()->addAction("dummy", action); KisAction* action2 = new KisAction("dummy", this); action2->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER); view->viewManager()->actionManager()->addAction("dummy", action2); view->viewManager()->actionManager()->updateGUI(); QVERIFY(!action->isEnabled()); QVERIFY(!action2->isEnabled()); KisPaintLayerSP paintLayer1 = new KisPaintLayer(doc->image(), "paintlayer1", OPACITY_OPAQUE_U8); doc->image()->addNode(paintLayer1); viewManager->nodeManager()->slotUiActivatedNode(paintLayer1); view->viewManager()->actionManager()->updateGUI(); QVERIFY(action->isEnabled()); QVERIFY(!action2->isEnabled()); } void KisActionManagerTest::testCondition() { KisDocument* doc = createEmptyDocument(); KisMainWindow* mainWindow = KisPart::instance()->createMainWindow(); QPointer view = new KisView(doc, mainWindow->resourceManager(), mainWindow->actionCollection(), mainWindow); KisViewManager *viewManager = new KisViewManager(mainWindow, mainWindow->actionCollection()); KisPart::instance()->addView(view); mainWindow->showView(view); view->setViewManager(viewManager); viewManager->setCurrentView(view); KisAction* action = new KisAction("dummy", this); action->setActivationFlags(KisAction::ACTIVE_DEVICE); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); view->viewManager()->actionManager()->addAction("dummy", action); KisPaintLayerSP paintLayer1 = new KisPaintLayer(doc->image(), "paintlayer1", OPACITY_OPAQUE_U8); doc->image()->addNode(paintLayer1); viewManager->nodeManager()->slotUiActivatedNode(paintLayer1); view->viewManager()->actionManager()->updateGUI(); QVERIFY(action->isEnabled()); // visible // paintLayer1->setVisible(false); // view->viewManager()->actionManager()->updateGUI(); // QVERIFY(!action->isEnabled()); paintLayer1->setVisible(true); view->viewManager()->actionManager()->updateGUI(); QVERIFY(action->isEnabled()); // locked paintLayer1->setUserLocked(true); view->viewManager()->actionManager()->updateGUI(); QVERIFY(!action->isEnabled()); paintLayer1->setUserLocked(false); view->viewManager()->actionManager()->updateGUI(); QVERIFY(action->isEnabled()); } void KisActionManagerTest::testTakeAction() { KisDocument* doc = createEmptyDocument(); KisMainWindow* mainWindow = KisPart::instance()->createMainWindow(); QPointer view = new KisView(doc, mainWindow->resourceManager(), mainWindow->actionCollection(), mainWindow); KisViewManager *viewManager = new KisViewManager(mainWindow, mainWindow->actionCollection()); KisPart::instance()->addView(view); mainWindow->showView(view); view->setViewManager(viewManager); viewManager->setCurrentView(view); KisAction* action = new KisAction("dummy", this); view->viewManager()->actionManager()->addAction("dummy", action); QVERIFY(view->viewManager()->actionManager()->actionByName("dummy") != 0); view->viewManager()->actionManager()->takeAction(action); QVERIFY(view->viewManager()->actionManager()->actionByName("dummy") == 0); } -QTEST_MAIN(KisActionManagerTest) +KISTEST_MAIN(KisActionManagerTest) diff --git a/libs/ui/tests/kis_animation_exporter_test.cpp b/libs/ui/tests/kis_animation_exporter_test.cpp index f44a2a3598..8747041cfd 100644 --- a/libs/ui/tests/kis_animation_exporter_test.cpp +++ b/libs/ui/tests/kis_animation_exporter_test.cpp @@ -1,93 +1,102 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_exporter_test.h" #include "dialogs/KisAsyncAnimationFramesSaveDialog.h" #include #include #include "KisPart.h" #include "kis_image.h" #include "KisDocument.h" #include "kis_image_animation_interface.h" #include "KoColor.h" #include #include "kis_time_range.h" #include "kis_keyframe_channel.h" - +#include +#include void KisAnimationExporterTest::testAnimationExport() { KisDocument *document = KisPart::instance()->createDocument(); QRect rect(0,0,512,512); QRect fillRect(10,0,502,512); TestUtil::MaskParent p(rect); document->setCurrentImage(p.image); const KoColorSpace *cs = p.image->colorSpace(); KUndo2Command parentCommand; p.layer->enableAnimation(); KisKeyframeChannel *rasterChannel = p.layer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); rasterChannel->addKeyframe(1, &parentCommand); rasterChannel->addKeyframe(2, &parentCommand); p.image->animationInterface()->setFullClipRange(KisTimeRange::fromTime(0, 2)); KisPaintDeviceSP dev = p.layer->paintDevice(); dev->fill(fillRect, KoColor(Qt::red, cs)); QImage frame0 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(1); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::green, cs)); QImage frame1 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(2); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::blue, cs)); QImage frame2 = dev->convertToQImage(0, rect); KisAsyncAnimationFramesSaveDialog exporter(document->image(), KisTimeRange::fromTime(0,2), "export-test.png", 0, 0); exporter.setBatchMode(true); exporter.regenerateRange(0); QTest::qWait(1000); QImage exported; + QPoint errpoint; exported.load("export-test0000.png"); - QCOMPARE(exported, frame0); + qDebug() << exported.size() << frame0.size(); + if (!TestUtil::compareQImages(errpoint, exported, frame0)) { + QFAIL(QString("Failed to export identical frame0, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } exported.load("export-test0001.png"); - QCOMPARE(exported, frame1); - - exported.load("export-test0003.png"); - QCOMPARE(exported, frame2); + if (!TestUtil::compareQImages(errpoint, exported, frame1)) { + QFAIL(QString("Failed to export identical frame1, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } + + exported.load("export-test0002.png"); + if (!TestUtil::compareQImages(errpoint, exported, frame2)) { + QFAIL(QString("Failed to export identical frame2, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } } QTEST_MAIN(KisAnimationExporterTest) diff --git a/libs/ui/tests/kis_derived_resources_test.cpp b/libs/ui/tests/kis_derived_resources_test.cpp index 81381ae948..947380cbe9 100644 --- a/libs/ui/tests/kis_derived_resources_test.cpp +++ b/libs/ui/tests/kis_derived_resources_test.cpp @@ -1,144 +1,147 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_derived_resources_test.h" #include #include #include #include #include "kis_canvas_resource_provider.h" #include #include #include #include #include #include #include #include - +#include #include "testutil.h" void addResourceTypes() { // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); + KisConfig cfg(false); + cfg.setUseOpenGL(false); + } void KisDerivedResourcesTest::test() { KisDocument* doc = createEmptyDocument(); addResourceTypes(); KisMainWindow* mainWindow = KisPart::instance()->createMainWindow(); QPointer view = new KisView(doc, mainWindow->resourceManager(), mainWindow->actionCollection(), mainWindow); KisViewManager *viewManager = new KisViewManager(mainWindow, mainWindow->actionCollection()); KoCanvasResourceManager *manager = viewManager->resourceProvider()->resourceManager(); QApplication::processEvents(); QString presetFileName = "autobrush_300px.kpp"; QVariant i; KisPaintOpPresetSP preset; if (!presetFileName.isEmpty()) { QString fullFileName = TestUtil::fetchDataFileLazy(presetFileName); preset = new KisPaintOpPreset(fullFileName); bool presetValid = preset->load(); Q_ASSERT(presetValid); Q_UNUSED(presetValid); i.setValue(preset); } QVERIFY(i.isValid()); QSignalSpy spy(manager, SIGNAL(canvasResourceChanged(int, const QVariant &))); manager->setResource(KisCanvasResourceProvider::CurrentPaintOpPreset, i); QCOMPARE(spy[0][0].toInt(), (int)KisCanvasResourceProvider::CurrentPaintOpPreset); QCOMPARE(spy[0][1].value(), preset); QCOMPARE(spy[1][0].toInt(), (int)KisCanvasResourceProvider::EraserMode); QCOMPARE(spy[1][1].toBool(), false); QCOMPARE(spy[2][0].toInt(), (int)KisCanvasResourceProvider::LodAvailability); QCOMPARE(spy[2][1].toBool(), true); QCOMPARE(spy[3][0].toInt(), (int)KisCanvasResourceProvider::Size); QCOMPARE(spy[3][1].toDouble(), 1.0); QCOMPARE(spy[4][0].toInt(), (int)KisCanvasResourceProvider::Flow); QCOMPARE(spy[4][1].toDouble(), 1.0); QCOMPARE(spy[5][0].toInt(), (int)KisCanvasResourceProvider::Opacity); QCOMPARE(spy[5][1].toDouble(), 1.0); QCOMPARE(spy[6][0].toInt(), (int)KisCanvasResourceProvider::CurrentEffectiveCompositeOp); QCOMPARE(spy[6][1].toString(), COMPOSITE_OVER); spy.clear(); preset->settings()->setPaintOpOpacity(0.8); QCOMPARE(spy.size(), 1); QCOMPARE(spy[0][0].toInt(), (int)KisCanvasResourceProvider::Opacity); QCOMPARE(spy[0][1].toDouble(), 0.8); spy.clear(); mainWindow->hide(); QApplication::processEvents(); delete view; delete doc; delete mainWindow; } -QTEST_MAIN(KisDerivedResourcesTest) +KISTEST_MAIN(KisDerivedResourcesTest) diff --git a/libs/ui/tests/kis_file_layer_test.cpp b/libs/ui/tests/kis_file_layer_test.cpp index 0607008b2e..10debd1d5c 100644 --- a/libs/ui/tests/kis_file_layer_test.cpp +++ b/libs/ui/tests/kis_file_layer_test.cpp @@ -1,146 +1,146 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_file_layer_test.h" #include #include #include #include #include #include void KisFileLayerTest::testFileLayerPlusTransformMaskOffImage() { TestUtil::ReferenceImageChecker chk("flayer_tmask_offimage", "file_layer"); QRect refRect(0,0,640,441); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); QString refName(TestUtil::fetchDataFileLazy("hakonepa.png")); KisLayerSP flayer = new KisFileLayer(p.image, "", refName, KisFileLayer::None, "flayer", OPACITY_OPAQUE_U8); p.image->addNode(flayer, p.image->root(), KisNodeSP()); QTest::qWait(2000); p.image->waitForDone(); KisTransformMaskSP mask1 = new KisTransformMask(); p.image->addNode(mask1, flayer); mask1->setName("mask1"); flayer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "00X_initial_layer_update"); flayer->setX(580); flayer->setY(400); flayer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "01_file_layer_moved"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "01X_file_layer_moved"); QTransform transform = QTransform::fromTranslate(-580, -400); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); /** * NOTE: here we see our image cropped by 1.5 image size rect! * That is expected and controlled by * KisImageConfig::transformMaskOffBoundsReadArea() * parameter */ mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "02_mask1_moved_mask_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "02X_mask1_moved_mask_update"); QVERIFY(chk.testPassed()); } void KisFileLayerTest::testFileLayerPlusTransformMaskSmallFileBigOffset() { TestUtil::ReferenceImageChecker chk("flayer_tmask_huge_offset", "file_layer"); QRect refRect(0,0,2000,1500); QRect fillRect(400,400,100,100); TestUtil::MaskParent p(refRect); QString refName(TestUtil::fetchDataFileLazy("file_layer_source.png")); KisLayerSP flayer = new KisFileLayer(p.image, "", refName, KisFileLayer::None, "flayer", OPACITY_OPAQUE_U8); p.image->addNode(flayer, p.image->root(), KisNodeSP()); QTest::qWait(2000); p.image->waitForDone(); // check whether the default bounds of the file layer are // initialized properly QCOMPARE(flayer->original()->defaultBounds()->bounds(), p.image->bounds()); KisTransformMaskSP mask1 = new KisTransformMask(); p.image->addNode(mask1, flayer); mask1->setName("mask1"); flayer->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "00X_initial_layer_update"); QTransform transform; transform = QTransform::fromTranslate(1200, 300); mask1->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(transform))); mask1->setDirty(refRect); p.image->waitForDone(); chk.checkImage(p.image, "01_mask1_moved_mask_update"); QTest::qWait(4000); p.image->waitForDone(); chk.checkImage(p.image, "01X_mask1_moved_mask_update"); QVERIFY(chk.testPassed()); } -QTEST_MAIN(KisFileLayerTest) +KISTEST_MAIN(KisFileLayerTest) diff --git a/libs/ui/tests/kis_input_manager_test.cpp b/libs/ui/tests/kis_input_manager_test.cpp index a072111913..05ae57c1e6 100644 --- a/libs/ui/tests/kis_input_manager_test.cpp +++ b/libs/ui/tests/kis_input_manager_test.cpp @@ -1,394 +1,394 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager_test.h" #include #include #include "input/kis_single_action_shortcut.h" #include "input/kis_stroke_shortcut.h" #include "input/kis_abstract_input_action.h" #include "input/kis_shortcut_matcher.h" void KisInputManagerTest::testSingleActionShortcut() { KisSingleActionShortcut s(0,0); - s.setKey(QList() << Qt::Key_Shift, Qt::Key_Space); + s.setKey(QSet() << Qt::Key_Shift, Qt::Key_Space); - QVERIFY(s.match(QList() << Qt::Key_Shift, Qt::Key_Space)); - QVERIFY(!s.match(QList() << Qt::Key_Control, Qt::Key_Space)); - QVERIFY(!s.match(QList(), Qt::Key_Space)); - QVERIFY(!s.match(QList() << Qt::Key_Shift, Qt::Key_Escape)); - QVERIFY(!s.match(QList() << Qt::Key_Shift, KisSingleActionShortcut::WheelUp)); + QVERIFY(s.match(QSet() << Qt::Key_Shift, Qt::Key_Space)); + QVERIFY(!s.match(QSet() << Qt::Key_Control, Qt::Key_Space)); + QVERIFY(!s.match(QSet(), Qt::Key_Space)); + QVERIFY(!s.match(QSet() << Qt::Key_Shift, Qt::Key_Escape)); + QVERIFY(!s.match(QSet() << Qt::Key_Shift, KisSingleActionShortcut::WheelUp)); - s.setWheel(QList() << Qt::Key_Shift, KisSingleActionShortcut::WheelUp); + s.setWheel(QSet() << Qt::Key_Shift, KisSingleActionShortcut::WheelUp); - QVERIFY(!s.match(QList() << Qt::Key_Shift, Qt::Key_Space)); - QVERIFY(!s.match(QList() << Qt::Key_Control, Qt::Key_Space)); - QVERIFY(!s.match(QList(), Qt::Key_Space)); - QVERIFY(!s.match(QList() << Qt::Key_Shift, Qt::Key_Escape)); - QVERIFY(s.match(QList() << Qt::Key_Shift, KisSingleActionShortcut::WheelUp)); + QVERIFY(!s.match(QSet() << Qt::Key_Shift, Qt::Key_Space)); + QVERIFY(!s.match(QSet() << Qt::Key_Control, Qt::Key_Space)); + QVERIFY(!s.match(QSet(), Qt::Key_Space)); + QVERIFY(!s.match(QSet() << Qt::Key_Shift, Qt::Key_Escape)); + QVERIFY(s.match(QSet() << Qt::Key_Shift, KisSingleActionShortcut::WheelUp)); } void KisInputManagerTest::testStrokeShortcut() { KisStrokeShortcut s(0,0); - s.setButtons(QList() << Qt::Key_Shift << Qt::Key_Control, - QList() << Qt::LeftButton); + s.setButtons(QSet() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::LeftButton); - QVERIFY(s.matchReady(QList() << Qt::Key_Shift << Qt::Key_Control, - QList() << Qt::LeftButton)); + QVERIFY(s.matchReady(QSet() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::LeftButton)); - QVERIFY(s.matchReady(QList() << Qt::Key_Shift << Qt::Key_Control, - QList())); + QVERIFY(s.matchReady(QSet() << Qt::Key_Shift << Qt::Key_Control, + QSet())); - QVERIFY(!s.matchReady(QList() << Qt::Key_Control << Qt::Key_Alt, - QList())); + QVERIFY(!s.matchReady(QSet() << Qt::Key_Control << Qt::Key_Alt, + QSet())); - QVERIFY(!s.matchReady(QList() << Qt::Key_Shift << Qt::Key_Control, - QList() << Qt::RightButton)); + QVERIFY(!s.matchReady(QSet() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::RightButton)); QVERIFY(s.matchBegin(Qt::LeftButton)); QVERIFY(!s.matchBegin(Qt::RightButton)); } struct TestingAction : public KisAbstractInputAction { TestingAction() : KisAbstractInputAction("TestingAction"), m_isHighResolution(false) { reset(); } ~TestingAction() {} void begin(int shortcut, QEvent *event) { m_beginIndex = shortcut; m_beginNonNull = event;} void end(QEvent *event) { m_ended = true; m_endNonNull = event; } void inputEvent(QEvent* event) { Q_UNUSED(event); m_gotInput = true; } void reset() { m_beginIndex = -1; m_ended = false; m_gotInput = false; m_beginNonNull = false; m_endNonNull = false; } bool supportsHiResInputEvents() const { return m_isHighResolution; } void setHighResInputEvents(bool value) { m_isHighResolution = value; } int m_beginIndex; bool m_ended; bool m_gotInput; bool m_beginNonNull; bool m_endNonNull; bool m_isHighResolution; }; KisSingleActionShortcut* createKeyShortcut(KisAbstractInputAction *action, int shortcutIndex, - const QList &modifiers, + const QSet &modifiers, Qt::Key key) { KisSingleActionShortcut *s = new KisSingleActionShortcut(action, shortcutIndex); s->setKey(modifiers, key); return s; } KisStrokeShortcut* createStrokeShortcut(KisAbstractInputAction *action, int shortcutIndex, - const QList &modifiers, + const QSet &modifiers, Qt::MouseButton button) { KisStrokeShortcut *s = new KisStrokeShortcut(action, shortcutIndex); - s->setButtons(modifiers, QList() << button); + s->setButtons(modifiers, QSet() << button); return s; } void KisInputManagerTest::testKeyEvents() { KisShortcutMatcher m; m.enterEvent(); TestingAction *a = new TestingAction(); m.addShortcut( createKeyShortcut(a, 10, - QList() << Qt::Key_Shift, + QSet() << Qt::Key_Shift, Qt::Key_Enter)); m.addShortcut( createKeyShortcut(a, 11, - QList() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::Key_Shift << Qt::Key_Control, Qt::Key_Enter)); m.addShortcut( createStrokeShortcut(a, 12, - QList() << Qt::Key_Shift, + QSet() << Qt::Key_Shift, Qt::RightButton)); m.addShortcut( createStrokeShortcut(a, 13, - QList() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::Key_Shift << Qt::Key_Control, Qt::LeftButton)); QCOMPARE(a->m_beginIndex, -1); // Test event with random values QMouseEvent mouseEvent(QEvent::MouseMove, QPoint(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); // Press Ctrl+Shift QVERIFY(!m.keyPressed(Qt::Key_Shift)); QCOMPARE(a->m_beginIndex, -1); QVERIFY(!m.keyPressed(Qt::Key_Control)); QCOMPARE(a->m_beginIndex, -1); // Complete Ctrl+Shift+Enter shortcut QVERIFY(m.keyPressed(Qt::Key_Enter)); QCOMPARE(a->m_beginIndex, 11); QCOMPARE(a->m_ended, true); QCOMPARE(a->m_beginNonNull, false); QCOMPARE(a->m_endNonNull, false); a->reset(); // Pressing mouse buttons is disabled since Enter is pressed QVERIFY(!m.buttonPressed(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QVERIFY(!m.buttonReleased(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); // Release Enter, so the system should be ready for new shortcuts QVERIFY(!m.keyReleased(Qt::Key_Enter)); QCOMPARE(a->m_beginIndex, -1); // Complete Ctrl+Shift+LB shortcut QVERIFY(m.buttonPressed(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, 13); QCOMPARE(a->m_ended, false); QCOMPARE(a->m_beginNonNull, true); QCOMPARE(a->m_endNonNull, false); a->reset(); QVERIFY(m.buttonReleased(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, true); QCOMPARE(a->m_beginNonNull, false); QCOMPARE(a->m_endNonNull, true); a->reset(); // There is no Ctrl+Shift+RB shortcut QVERIFY(!m.buttonPressed(Qt::RightButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QVERIFY(!m.buttonReleased(Qt::RightButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); // Check that Ctrl+Shift+Enter is still enabled QVERIFY(m.keyPressed(Qt::Key_Enter)); QCOMPARE(a->m_beginIndex, 11); QCOMPARE(a->m_ended, true); QCOMPARE(a->m_beginNonNull, false); QCOMPARE(a->m_endNonNull, false); a->reset(); // Check autorepeat QVERIFY(m.autoRepeatedKeyPressed(Qt::Key_Enter)); QCOMPARE(a->m_beginIndex, 11); QCOMPARE(a->m_ended, true); QCOMPARE(a->m_beginNonNull, false); QCOMPARE(a->m_endNonNull, false); a->reset(); QVERIFY(!m.keyReleased(Qt::Key_Enter)); QCOMPARE(a->m_beginIndex, -1); // Release Ctrl QVERIFY(!m.keyReleased(Qt::Key_Control)); QCOMPARE(a->m_beginIndex, -1); // There is no Shift+LB shortcut QVERIFY(!m.buttonPressed(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QVERIFY(!m.buttonReleased(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); // But there *is* Shift+RB shortcut QVERIFY(m.buttonPressed(Qt::RightButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, 12); QCOMPARE(a->m_ended, false); QCOMPARE(a->m_beginNonNull, true); QCOMPARE(a->m_endNonNull, false); a->reset(); QVERIFY(m.buttonReleased(Qt::RightButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, true); QCOMPARE(a->m_beginNonNull, false); QCOMPARE(a->m_endNonNull, true); a->reset(); // Check that Shift+Enter still works QVERIFY(m.keyPressed(Qt::Key_Enter)); QCOMPARE(a->m_beginIndex, 10); QCOMPARE(a->m_ended, true); QCOMPARE(a->m_beginNonNull, false); QCOMPARE(a->m_endNonNull, false); a->reset(); m.leaveEvent(); } void KisInputManagerTest::testReleaseUnnecessaryModifiers() { KisShortcutMatcher m; m.enterEvent(); TestingAction *a = new TestingAction(); m.addShortcut( createStrokeShortcut(a, 13, - QList() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::Key_Shift << Qt::Key_Control, Qt::LeftButton)); // Test event with random values QMouseEvent mouseEvent(QEvent::MouseMove, QPoint(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); // Press Ctrl+Shift QVERIFY(!m.keyPressed(Qt::Key_Shift)); QCOMPARE(a->m_beginIndex, -1); QVERIFY(!m.keyPressed(Qt::Key_Control)); QCOMPARE(a->m_beginIndex, -1); // Complete Ctrl+Shift+LB shortcut QVERIFY(m.buttonPressed(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, 13); QCOMPARE(a->m_ended, false); a->reset(); // Release Ctrl QVERIFY(!m.keyReleased(Qt::Key_Control)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, false); // Release Shift QVERIFY(!m.keyReleased(Qt::Key_Shift)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, false); // Release LB, now it should end QVERIFY(m.buttonReleased(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, true); a->reset(); m.leaveEvent(); } void KisInputManagerTest::testMouseMoves() { KisShortcutMatcher m; m.enterEvent(); TestingAction *a = new TestingAction(); m.addShortcut( createStrokeShortcut(a, 13, - QList() << Qt::Key_Shift << Qt::Key_Control, + QSet() << Qt::Key_Shift << Qt::Key_Control, Qt::LeftButton)); // Test event with random values QMouseEvent mouseEvent(QEvent::MouseMove, QPoint(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); // Press Ctrl+Shift QVERIFY(!m.keyPressed(Qt::Key_Shift)); QCOMPARE(a->m_beginIndex, -1); QVERIFY(!m.keyPressed(Qt::Key_Control)); QCOMPARE(a->m_beginIndex, -1); - QVERIFY(!m.cursorMoved(&mouseEvent)); + QVERIFY(!m.pointerMoved(&mouseEvent)); QCOMPARE(a->m_gotInput, false); // Complete Ctrl+Shift+LB shortcut QVERIFY(m.buttonPressed(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, 13); QCOMPARE(a->m_ended, false); QCOMPARE(a->m_gotInput, false); a->reset(); - QVERIFY(m.mouseMoved(&mouseEvent)); + QVERIFY(m.pointerMoved(&mouseEvent)); QCOMPARE(a->m_gotInput, true); a->reset(); // Release Ctrl QVERIFY(!m.keyReleased(Qt::Key_Control)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, false); QCOMPARE(a->m_gotInput, false); // Release Shift QVERIFY(!m.keyReleased(Qt::Key_Shift)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, false); // Release LB, now it should end QVERIFY(m.buttonReleased(Qt::LeftButton, &mouseEvent)); QCOMPARE(a->m_beginIndex, -1); QCOMPARE(a->m_ended, true); a->reset(); m.leaveEvent(); } #include "../input/wintab/kis_incremental_average.h" void KisInputManagerTest::testIncrementalAverage() { KisIncrementalAverage avg(3); QCOMPARE(avg.pushThrough(10), 10); QCOMPARE(avg.pushThrough(20), 13); QCOMPARE(avg.pushThrough(30), 20); QCOMPARE(avg.pushThrough(30), 26); QCOMPARE(avg.pushThrough(30), 30); } QTEST_MAIN(KisInputManagerTest) diff --git a/libs/ui/tests/kis_node_manager_test.cpp b/libs/ui/tests/kis_node_manager_test.cpp index b1685a86e7..c8c293d083 100644 --- a/libs/ui/tests/kis_node_manager_test.cpp +++ b/libs/ui/tests/kis_node_manager_test.cpp @@ -1,291 +1,292 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager_test.h" #include #include +#include #include "ui_manager_test.h" class NodeManagerTester : public TestUtil::UiManagerTest { public: NodeManagerTester() : UiManagerTest(false, true, "node_manager_test") { nodeManager = view->nodeManager(); } void activateShapeLayer() { KisNodeSP shape = findNode(image->root(), "shape"); Q_ASSERT(shape); nodeManager->slotNonUiActivatedNode(shape); } KisNodeSP findCloneLayer() { return findNode(image->root(), "clone1"); } void activateCloneLayer() { KisNodeSP node = findCloneLayer(); Q_ASSERT(node); nodeManager->slotNonUiActivatedNode(findCloneLayer()); } KisNodeSP findBlurLayer() { return findNode(image->root(), "blur1"); } void activateBlurLayer() { KisNodeSP node = findBlurLayer(); Q_ASSERT(node); nodeManager->slotNonUiActivatedNode(findBlurLayer()); } void checkUndoWait() { undoStore->undo(); QTest::qWait(1000); image->waitForDone(); QVERIFY(checkLayersInitial()); } KisNodeManager *nodeManager; }; void testRotateNode(bool useShapeLayer, const QString &name) { NodeManagerTester t; if(useShapeLayer) { t.activateShapeLayer(); } t.nodeManager->rotate(M_PI / 6.0); QTest::qWait(1000); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy(name)); t.checkUndoWait(); t.startConcurrentTask(); t.nodeManager->rotate(M_PI / 6.0); QTest::qWait(1000); t.image->waitForDone(); if (!useShapeLayer) { QEXPECT_FAIL("", "The user may run Rotate Layer concurrently. It will cause wrong image/selection size fetched for the crop. There is some barrier needed. At least it doesn't crash.", Continue); } QVERIFY(t.checkLayersFuzzy(name)); } void testShearNode(bool useShapeLayer, const QString &name) { NodeManagerTester t; if(useShapeLayer) { t.activateShapeLayer(); } t.nodeManager->shear(30, 0); QTest::qWait(1000); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy(name)); t.checkUndoWait(); t.startConcurrentTask(); t.nodeManager->shear(30, 0); QTest::qWait(1000); t.image->waitForDone(); QEXPECT_FAIL("", "The user may run Shear Layer concurrently. It will cause wrong image/selection size fetched for the crop. There is some barrier needed. At least it doesn't crash.", Continue); QVERIFY(t.checkLayersFuzzy(name)); } void testScaleNode(bool useShapeLayer, const QString &name) { KisFilterStrategy *strategy = new KisBicubicFilterStrategy(); NodeManagerTester t; if(useShapeLayer) { t.activateShapeLayer(); } t.nodeManager->scale(0.5, 0.5, strategy); QTest::qWait(1000); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy(name)); t.checkUndoWait(); t.startConcurrentTask(); t.nodeManager->scale(0.5, 0.5, strategy); QTest::qWait(1000); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy(name)); delete strategy; } void testMirrorNode(bool useShapeLayer, const QString &name, bool mirrorX) { NodeManagerTester t; if(useShapeLayer) { t.activateShapeLayer(); } if (mirrorX) { t.nodeManager->mirrorNodeX(); } else { t.nodeManager->mirrorNodeY(); } QTest::qWait(1000); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy(name)); t.checkUndoWait(); t.startConcurrentTask(); if (mirrorX) { t.nodeManager->mirrorNodeX(); } else { t.nodeManager->mirrorNodeY(); } QTest::qWait(1000); t.image->waitForDone(); QEXPECT_FAIL("", "The user may run Mirror Layer concurrently, but it is not ported to strokes yet. At least it doesn't crash.", Continue); QVERIFY(t.checkLayersFuzzy(name)); } void KisNodeManagerTest::testRotatePaintNode() { testRotateNode(false, "paint_rotated_30"); } void KisNodeManagerTest::testShearPaintNode() { testShearNode(false, "paint_shear_30"); } void KisNodeManagerTest::testScalePaintNode() { testScaleNode(false, "paint_scale_0.5"); } void KisNodeManagerTest::testMirrorXPaintNode() { testMirrorNode(false, "paint_mirrorX", true); } void KisNodeManagerTest::testMirrorYPaintNode() { testMirrorNode(false, "paint_mirrorY", false); } void KisNodeManagerTest::testRotateShapeNode() { testRotateNode(true, "shape_rotated_30"); } void KisNodeManagerTest::testShearShapeNode() { testShearNode(true, "shape_shear_30"); } void KisNodeManagerTest::testScaleShapeNode() { testScaleNode(true, "shape_scale_0.5"); } void KisNodeManagerTest::testMirrorShapeNode() { testMirrorNode(true, "shape_mirrorX", true); } void KisNodeManagerTest::testConvertCloneToPaintLayer() { NodeManagerTester t; t.activateCloneLayer(); QVERIFY(t.checkLayersInitial()); t.nodeManager->convertNode("KisPaintLayer"); KisNodeSP node = t.findCloneLayer(); QTest::qWait(1000); t.image->waitForDone(); QVERIFY(dynamic_cast(node.data())); // No visible change should happen! QVERIFY(t.checkLayersInitial()); } void testConvertToSelectionMask(bool fromClone) { NodeManagerTester t; if (fromClone) { t.activateCloneLayer(); } else { t.activateBlurLayer(); } QVERIFY(t.checkLayersInitial()); QVERIFY(!t.image->globalSelection()); t.nodeManager->convertNode("KisSelectionMask"); QTest::qWait(1000); t.image->waitForDone(); KisNodeSP node; if (fromClone) { node = t.findCloneLayer(); } else { node = t.findBlurLayer(); } QVERIFY(!node); KisSelectionSP selection = t.image->globalSelection(); QVERIFY(selection); QVERIFY(!selection->outlineCacheValid() || !selection->outlineCache().isEmpty()); QString testName = fromClone ? "selection_from_clone_layer" : "selection_from_blur_layer"; QVERIFY(t.checkSelectionOnly(testName)); } void KisNodeManagerTest::testConvertCloneToSelectionMask() { testConvertToSelectionMask(true); } void KisNodeManagerTest::testConvertBlurToSelectionMask() { testConvertToSelectionMask(false); } -QTEST_MAIN(KisNodeManagerTest) +KISTEST_MAIN(KisNodeManagerTest) diff --git a/libs/ui/tests/kis_node_model_test.cpp b/libs/ui/tests/kis_node_model_test.cpp index 5803d6f491..2e6e55cd5b 100644 --- a/libs/ui/tests/kis_node_model_test.cpp +++ b/libs/ui/tests/kis_node_model_test.cpp @@ -1,114 +1,113 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_model_test.h" #include #include #include "KisDocument.h" #include "KisPart.h" #include "kis_node_model.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" +#include #include "modeltest.h" void KisNodeModelTest::init() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); m_shapeController = new KisShapeController(m_doc, m_nameServer); m_nodeModel = new KisNodeModel(0); initBase(); } void KisNodeModelTest::cleanup() { cleanupBase(); delete m_nodeModel; delete m_shapeController; delete m_nameServer; delete m_doc; } void KisNodeModelTest::testSetImage() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); } void KisNodeModelTest::testAddNode() { m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); constructImage(); } void KisNodeModelTest::testRemoveAllNodes() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); m_image->removeNode(m_layer4); m_image->removeNode(m_layer3); m_image->removeNode(m_layer2); m_image->removeNode(m_layer1); } void KisNodeModelTest::testRemoveIncludingRoot() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); m_image->removeNode(m_layer4); m_image->removeNode(m_layer3); m_image->removeNode(m_layer2); m_image->removeNode(m_layer1); m_image->removeNode(m_image->root()); } void KisNodeModelTest::testSubstituteRootNode() { constructImage(); m_shapeController->setImage(m_image); m_nodeModel->setDummiesFacade(m_shapeController, m_image, 0, 0, 0); new ModelTest(m_nodeModel, this); m_image->flatten(); } -QTEST_MAIN(KisNodeModelTest) - - +KISTEST_MAIN(KisNodeModelTest) diff --git a/libs/ui/tests/kis_node_view_test.cpp b/libs/ui/tests/kis_node_view_test.cpp index 0074c2031c..8eef6d7da7 100644 --- a/libs/ui/tests/kis_node_view_test.cpp +++ b/libs/ui/tests/kis_node_view_test.cpp @@ -1,120 +1,121 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_view_test.h" #include #include #include #include #include #include #include "KisDocument.h" #include "KisPart.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" #include "kis_undo_adapter.h" #include "kis_node_model.h" #include "kis_color_filter_combo.h" +#include void KisNodeViewTest::init() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); m_shapeController = new KisShapeController(m_doc, m_nameServer); initBase(); } void KisNodeViewTest::cleanup() { cleanupBase(); delete m_shapeController; delete m_nameServer; delete m_doc; } void KisNodeViewTest::testLayers() { QDialog dlg; QFont font; font.setPointSizeF(8); dlg.setFont(font); KisNodeModel *model = new KisNodeModel(this); KisNodeView *view = new KisNodeView(&dlg); view->setModel(model); constructImage(); addSelectionMasks(); m_shapeController->setImage(m_image); model->setDummiesFacade(m_shapeController, m_image, m_shapeController, 0, 0); QVBoxLayout *layout = new QVBoxLayout(&dlg); KisColorFilterCombo *cb = new KisColorFilterCombo(&dlg); QSet labels; for (int i = 0; i < 6; i++) { labels.insert(i); } cb->updateAvailableLabels(labels); QHBoxLayout *hbox = new QHBoxLayout(&dlg); hbox->addStretch(1); hbox->addWidget(cb); layout->addLayout(hbox); layout->addWidget(view); dlg.resize(280, 400); view->expandAll(); dlg.exec(); } #include "kis_color_label_selector_widget.h" void KisNodeViewTest::testColorLabels() { QDialog dlg; QFont font; font.setPointSizeF(8); dlg.setFont(font); KisColorLabelSelectorWidget *widget = new KisColorLabelSelectorWidget(&dlg); QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred); widget->setSizePolicy(policy); QVBoxLayout *layout = new QVBoxLayout(&dlg); layout->addWidget(widget); layout->addStretch(1); dlg.resize(280, 400); dlg.exec(); } -QTEST_MAIN(KisNodeViewTest) +KISTEST_MAIN(KisNodeViewTest) diff --git a/libs/ui/tests/kis_selection_manager_test.cpp b/libs/ui/tests/kis_selection_manager_test.cpp index d35731b86c..53ecd952d3 100644 --- a/libs/ui/tests/kis_selection_manager_test.cpp +++ b/libs/ui/tests/kis_selection_manager_test.cpp @@ -1,418 +1,420 @@ /* * 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_selection_manager_test.h" #include #include +#include + #include "ui_manager_test.h" class SelectionManagerTester : public TestUtil::UiManagerTest { public: SelectionManagerTester(bool useSelection) : UiManagerTest(useSelection, false, "selection_manager_test") { Q_ASSERT(selectionManager); } }; void KisSelectionManagerTest::testFillForegroundWithoutSelection() { SelectionManagerTester t(false); t.selectionManager->fillForegroundColor(); t.image->waitForDone(); QVERIFY(t.checkLayers("fill_foreground_without_selection")); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->fillForegroundColor(); t.image->waitForDone(); QVERIFY(t.checkLayers("fill_foreground_without_selection")); } void KisSelectionManagerTest::testFillForegroundWithSelection() { SelectionManagerTester t(true); t.selectionManager->fillForegroundColor(); t.image->waitForDone(); QVERIFY(t.checkLayers("fill_foreground_with_selection")); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->fillForegroundColor(); t.image->waitForDone(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); QVERIFY(t.checkLayers("fill_foreground_with_selection")); } void KisSelectionManagerTest::testFillBackgroundWithSelection() { SelectionManagerTester t(true); t.selectionManager->fillBackgroundColor(); t.image->waitForDone(); QVERIFY(t.checkLayers("fill_background_with_selection")); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->fillBackgroundColor(); t.image->waitForDone(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); QVERIFY(t.checkLayers("fill_background_with_selection")); } void KisSelectionManagerTest::testFillPatternWithSelection() { SelectionManagerTester t(true); t.selectionManager->fillPattern(); t.image->waitForDone(); QVERIFY(t.checkLayers("fill_pattern_with_selection")); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->fillPattern(); t.image->waitForDone(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); QVERIFY(t.checkLayers("fill_pattern_with_selection")); } void KisSelectionManagerTest::testResizeToSelection() { SelectionManagerTester t(true); t.selectionManager->imageResizeToSelection(); t.image->waitForDone(); QVERIFY(t.checkLayers("resize_to_selection")); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->imageResizeToSelection(); t.image->waitForDone(); QEXPECT_FAIL("", "The user may run Resize to Selection concurrently. It will cause wrong image/selection size fetched for the crop. There is some barrier needed. At least it doesn't crash.", Continue); QVERIFY(t.checkLayers("resize_to_selection")); } void KisSelectionManagerTest::testSelectAll() { SelectionManagerTester t(true); t.selectionManager->selectAll(); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("select_all")); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->selectAll(); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("select_all")); } void KisSelectionManagerTest::testDeselectReselect() { SelectionManagerTester t(true); t.selectionManager->deselect(); t.image->waitForDone(); QVERIFY(t.checkNoSelection()); t.checkUndo(); t.startConcurrentTask(); t.selectionManager->deselect(); t.image->waitForDone(); QVERIFY(t.checkNoSelection()); t.selectionManager->reselect(); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("initial")); t.undoStore->undo(); t.image->waitForDone(); QVERIFY(t.checkNoSelection()); t.startConcurrentTask(); t.selectionManager->reselect(); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("initial")); } void KisSelectionManagerTest::testCopyPaste() { SelectionManagerTester t(true); t.selectionManager->copy(); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayers("copy_paste")); t.checkUndo(); t.startConcurrentTask(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); t.selectionManager->copy(); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayers("copy_paste")); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); t.checkUndo(); t.startConcurrentTask(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayers("copy_paste")); } void KisSelectionManagerTest::testCopyPasteMerged() { SelectionManagerTester t(true); t.selectionManager->copyMerged(); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy("copy_paste_merged")); t.checkUndo(); t.startConcurrentTask(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); t.selectionManager->copyMerged(); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy("copy_paste_merged")); } void KisSelectionManagerTest::testCutPaste() { SelectionManagerTester t(true); t.selectionManager->cut(); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayers("cut_paste")); t.checkDoubleUndo(); t.startConcurrentTask(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); t.selectionManager->cut(); t.selectionManager->paste(); t.image->waitForDone(); QVERIFY(t.checkLayers("cut_paste")); } void KisSelectionManagerTest::testInvertSelection() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("invertselection"); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkLayers("invert_selection")); t.checkUndo(); t.startConcurrentTask(); QEXPECT_FAIL("", "Fix some race condition on clone layers!", Continue); config = new KisOperationConfiguration("invertselection"); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkLayers("invert_selection")); } void KisSelectionManagerTest::testFeatherSelection() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("featherselection"); config->setProperty("radius", 10); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("feather_selection")); t.checkUndo(); t.startConcurrentTask(); config = new KisOperationConfiguration("featherselection"); config->setProperty("radius", 10); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("feather_selection")); } void KisSelectionManagerTest::testGrowSelectionSimplified() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("growselection"); config->setProperty("x-radius", 10); config->setProperty("y-radius", 5); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("grow_selection")); } void KisSelectionManagerTest::testShrinkSelectionUnlockedSimplified() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("shrinkselection"); config->setProperty("x-radius", 10); config->setProperty("y-radius", 5); config->setProperty("edgeLock", false); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("shrink_selection_unlocked")); } void KisSelectionManagerTest::testShrinkSelectionLockedSimplified() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("shrinkselection"); config->setProperty("x-radius", 10); config->setProperty("y-radius", 5); config->setProperty("edgeLock", true); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("shrink_selection_locked")); } void KisSelectionManagerTest::testSmoothSelectionSimplified() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("smoothselection"); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("smooth_selection")); } void KisSelectionManagerTest::testErodeSelectionSimplified() { // SelectionManagerTester t(true); // // t.selectionManager->erode(); // t.image->waitForDone(); // QVERIFY(t.checkSelectionOnly("erode_selection")); } void KisSelectionManagerTest::testDilateSelectionSimplified() { // SelectionManagerTester t(true); // // t.selectionManager->dilate(); // t.image->waitForDone(); // QVERIFY(t.checkSelectionOnly("dilate_selection")); } void KisSelectionManagerTest::testBorderSelectionSimplified() { SelectionManagerTester t(true); KisOperationConfigurationSP config = new KisOperationConfiguration("borderselection"); config->setProperty("x-radius", 10); config->setProperty("y-radius", 5); t.actionManager->runOperationFromConfiguration(config); t.image->waitForDone(); QVERIFY(t.checkSelectionOnly("border_selection")); } #include void KisSelectionManagerTest::testScanline16bit() { const int THRESHOLD = 20; QString fileName = TestUtil::fetchDataFileLazy("flood_fill_16bit.kra"); QVERIFY(QFile::exists(fileName)); KisDocument *doc = KisPart::instance()->createDocument(); doc->loadNativeFormat(fileName); KisPaintDeviceSP dev = doc->image()->root()->firstChild()->paintDevice(); QVERIFY(dev); dbgKrita << ppVar(dev->colorSpace()); QRect imageRect = doc->image()->bounds(); dbgKrita << ppVar(imageRect); QPoint startPoint = imageRect.center(); dbgKrita << ppVar(startPoint); KisPixelSelectionSP pixelSelection = new KisPixelSelection(); { KisScanlineFill gc(dev, startPoint, imageRect); gc.setThreshold(THRESHOLD); gc.fillSelection(pixelSelection); QImage resultImage = pixelSelection->convertToQImage(0, imageRect); QVERIFY(TestUtil::checkQImage(resultImage, "selection_manager_test", "scanline", "16bit_thres_20")); } const KoColorSpace *rgb8CS = KoColorSpaceRegistry::instance()->rgb8(); pixelSelection->clear(); dev->convertTo(rgb8CS); { KisScanlineFill gc(dev, startPoint, imageRect); gc.setThreshold(THRESHOLD); gc.fillSelection(pixelSelection); QImage resultImage = pixelSelection->convertToQImage(0, imageRect); QVERIFY(TestUtil::checkQImage(resultImage, "selection_manager_test", "scanline", "8bit_thres_20")); } } -QTEST_MAIN(KisSelectionManagerTest) +KISTEST_MAIN(KisSelectionManagerTest) diff --git a/libs/ui/tests/kis_shape_commands_test.cpp b/libs/ui/tests/kis_shape_commands_test.cpp index 109aa4af85..53a1d8f48c 100644 --- a/libs/ui/tests/kis_shape_commands_test.cpp +++ b/libs/ui/tests/kis_shape_commands_test.cpp @@ -1,229 +1,229 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_commands_test.h" #include #include "kis_global.h" #include "kis_shape_layer.h" #include #include #include "testutil.h" #include #include #include #include - +#include void KisShapeCommandsTest::testGrouping() { TestUtil::ReferenceImageChecker chk("grouping", "shape_commands_test"); QRect refRect(0,0,64,64); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(55, 55)); path->lineTo(QPointF(55, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer->addShape(path); } { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(30, 30)); path->lineTo(QPointF(30, 60)); path->lineTo(QPointF(60, 60)); path->lineTo(QPointF(60, 30)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName("shape2"); path->setZIndex(2); shapeLayer->addShape(path); } p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QList shapes = shapeLayer->shapes(); KoShapeGroup *group = new KoShapeGroup(); group->setName("group_shape"); shapeLayer->addShape(group); QScopedPointer cmd( new KoShapeGroupCommand(group, shapes, true)); cmd->redo(); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); cmd->undo(); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QVERIFY(chk.testPassed()); } void KisShapeCommandsTest::testResizeShape(bool normalizeGroup) { TestUtil::ReferenceImageChecker chk("resize_shape", "shape_commands_test"); QRect refRect(0,0,64,64); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(55, 55)); path->lineTo(QPointF(55, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer->addShape(path); } { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(30, 30)); path->lineTo(QPointF(30, 60)); path->lineTo(QPointF(60, 60)); path->lineTo(QPointF(60, 30)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName("shape2"); path->setZIndex(2); shapeLayer->addShape(path); } p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); QList shapes = shapeLayer->shapes(); KoShapeGroup *group = new KoShapeGroup(); group->setName("group_shape"); shapeLayer->addShape(group); QScopedPointer cmd( new KoShapeGroupCommand(group, shapes, normalizeGroup)); cmd->redo(); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); qDebug() << "Before:"; qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeft)); qDebug() << ppVar(group->absolutePosition(KoFlake::BottomRight)); qDebug() << ppVar(group->outlineRect()); qDebug() << ppVar(group->transformation()); QCOMPARE(group->absolutePosition(KoFlake::TopLeft), QPointF(5,5)); QCOMPARE(group->absolutePosition(KoFlake::BottomRight), QPointF(60,60)); const QPointF stillPoint = group->absolutePosition(KoFlake::BottomRight); KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, false, true, QTransform()); qDebug() << "After:"; qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeft)); qDebug() << ppVar(group->absolutePosition(KoFlake::BottomRight)); qDebug() << ppVar(group->outlineRect()); qDebug() << ppVar(group->transformation()); QCOMPARE(group->absolutePosition(KoFlake::TopLeft), QPointF(-6,-17)); QCOMPARE(group->absolutePosition(KoFlake::BottomRight), QPointF(60,60)); } void KisShapeCommandsTest::testResizeShape() { testResizeShape(false); } void KisShapeCommandsTest::testResizeShapeNormalized() { testResizeShape(true); } -QTEST_MAIN(KisShapeCommandsTest) +KISTEST_MAIN(KisShapeCommandsTest) diff --git a/libs/ui/tests/kis_shape_controller_test.cpp b/libs/ui/tests/kis_shape_controller_test.cpp index 217331d699..0abe419524 100644 --- a/libs/ui/tests/kis_shape_controller_test.cpp +++ b/libs/ui/tests/kis_shape_controller_test.cpp @@ -1,49 +1,50 @@ /* * 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_test.h" #include #include "KisPart.h" #include "KisDocument.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" +#include KisShapeControllerTest::~KisShapeControllerTest() { } KisDummiesFacadeBase* KisShapeControllerTest::dummiesFacadeFactory() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); return new KisShapeController(m_doc, m_nameServer); } void KisShapeControllerTest::destroyDummiesFacade(KisDummiesFacadeBase *dummiesFacade) { delete dummiesFacade; delete m_nameServer; delete m_doc; } -QTEST_MAIN(KisShapeControllerTest) +KISTEST_MAIN(KisShapeControllerTest) diff --git a/libs/ui/tests/kis_shape_layer_test.cpp b/libs/ui/tests/kis_shape_layer_test.cpp index 39620caa91..d857baf45a 100644 --- a/libs/ui/tests/kis_shape_layer_test.cpp +++ b/libs/ui/tests/kis_shape_layer_test.cpp @@ -1,309 +1,310 @@ /* * Copyright (c) 2018 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_shape_layer_test.h" #include #include "kis_global.h" #include "kis_shape_layer.h" #include #include #include "testutil.h" #include #include #include #include #include "kis_filter_strategy.h" #include "kis_layer_utils.h" +#include void testMergeDownImpl(bool useImageTransformations) { const QString testName = useImageTransformations ? "scale_and_merge_down" : "merge_down"; using namespace TestUtil; ReferenceImageChecker chk(testName, "shape_layer_test", ReferenceImageChecker::InternalStorage); chk.setMaxFailingPixels(10); QScopedPointer doc(KisPart::instance()->createDocument()); const QRect refRect(0,0,64,64); MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer1 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 150); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(20, 55)); path->lineTo(QPointF(20, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer1->addShape(path); } KisShapeLayerSP shapeLayer2 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer2", 255); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(15, 10)); path->lineTo(QPointF(15, 55)); path->lineTo(QPointF(50, 55)); path->lineTo(QPointF(50, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName("shape2"); path->setZIndex(1); shapeLayer2->addShape(path); } p.image->addNode(shapeLayer1); p.image->addNode(shapeLayer2); shapeLayer1->setDirty(); shapeLayer2->setDirty(); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 3); chk.checkImage(p.image, "00_initial_layer_update"); if (useImageTransformations) { KisFilterStrategy *strategy = new KisBilinearFilterStrategy(); p.image->scaleImage(QSize(32, 32), p.image->xRes(), p.image->yRes(), strategy); p.waitForImageAndShapeLayers(); chk.checkImage(p.image, "01_after_scale_down"); } p.image->mergeDown(shapeLayer2, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 2); KisShapeLayer *newShapeLayer = dynamic_cast(p.image->root()->lastChild().data()); QVERIFY(newShapeLayer); QVERIFY(newShapeLayer != shapeLayer1.data()); QVERIFY(newShapeLayer != shapeLayer2.data()); chk.checkImage(p.image, "02_after_merge_down"); QVERIFY(chk.testPassed()); } void KisShapeLayerTest::testMergeDown() { testMergeDownImpl(false); } void KisShapeLayerTest::testScaleAndMergeDown() { testMergeDownImpl(true); } namespace { KoPathShape* createSimpleShape(int zIndex) { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(15, 10)); path->lineTo(QPointF(15, 55)); path->lineTo(QPointF(50, 55)); path->lineTo(QPointF(50, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName(QString("shape_%1").arg(zIndex)); path->setZIndex(zIndex); return path; } } #include "commands/KoShapeReorderCommand.h" #include void testMergingShapeZIndexesImpl(int firstIndexStart, int firstIndexStep, int firstIndexSize, int secondIndexStart, int secondIndexStep, int secondIndexSize) { QList shapesBelow; QList shapesAbove; qDebug() << "Test zIndex merge:"; qDebug() << " " << ppVar(firstIndexStart) << ppVar(firstIndexStep) << ppVar(firstIndexSize); qDebug() << " " << ppVar(secondIndexStart) << ppVar(secondIndexStep) << ppVar(secondIndexSize); for (int i = 0; i < firstIndexSize; i++) { shapesBelow.append(createSimpleShape(firstIndexStart + firstIndexStep * i)); } for (int i = 0; i < secondIndexSize; i++) { shapesAbove.append(createSimpleShape(secondIndexStart + secondIndexStep * i)); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); for (int i = 0; i < shapesBelow.size(); i++) { if (i > 0 && shapesBelow[i - 1]->zIndex() >= shapesBelow[i]->zIndex()) { qDebug() << ppVar(i); qDebug() << ppVar(shapesBelow[i - 1]->zIndex()) << ppVar(shapesBelow[i]->zIndex()); QFAIL("Shapes have wrong ordering after merge!"); } } if (!shapesBelow.isEmpty() && !shapesAbove.isEmpty()) { if (shapesBelow.last()->zIndex() >= shapesAbove.first()->zIndex()) { qDebug() << ppVar(shapesBelow.last()->zIndex()) << ppVar(shapesAbove.first()->zIndex()); QFAIL("Two shape groups have intersections after merge!"); } } for (int i = 0; i < shapesAbove.size(); i++) { if (i > 0 && shapesAbove[i - 1]->zIndex() >= shapesAbove[i]->zIndex()) { qDebug() << ppVar(i); qDebug() << ppVar(shapesAbove[i - 1]->zIndex()) << ppVar(shapesAbove[i]->zIndex()); QFAIL("Shapes have wrong ordering after merge!"); } } } void KisShapeLayerTest::testMergingShapeZIndexes() { testMergingShapeZIndexesImpl(0, 1, 10, 5, 1, 10); testMergingShapeZIndexesImpl(0, 1, 0, 5, 1, 10); testMergingShapeZIndexesImpl(0, 1, 10, 5, 1, 0); testMergingShapeZIndexesImpl(std::numeric_limits::max() - 10, 1, 10, 5, 1, 10); testMergingShapeZIndexesImpl(-32768, 1024, 64, 0, 1024, 31); testMergingShapeZIndexesImpl(-32768+1023, 1024, 64, 0, 1, 1024); } void KisShapeLayerTest::testCloneScaledLayer() { using namespace TestUtil; ReferenceImageChecker chk("scale_and_clone", "shape_layer_test", ReferenceImageChecker::InternalStorage); chk.setMaxFailingPixels(10); QScopedPointer doc(KisPart::instance()->createDocument()); const QRect refRect(0,0,64,64); MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer1 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 150); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(20, 55)); path->lineTo(QPointF(20, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer1->addShape(path); } p.image->addNode(shapeLayer1); shapeLayer1->setDirty(); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 2); chk.checkImage(p.image, "00_initial_layer_update"); { KisFilterStrategy *strategy = new KisBilinearFilterStrategy(); p.image->scaleImage(QSize(32, 32), p.image->xRes(), p.image->yRes(), strategy); p.waitForImageAndShapeLayers(); chk.checkImage(p.image, "01_after_scale_down"); } KisNodeSP clonedLayer = shapeLayer1->clone(); p.image->removeNode(shapeLayer1); p.image->addNode(clonedLayer); clonedLayer->setDirty(); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 2); chk.checkImage(p.image, "01_after_scale_down"); QVERIFY(chk.testPassed()); } -QTEST_MAIN(KisShapeLayerTest) +KISTEST_MAIN(KisShapeLayerTest) diff --git a/libs/ui/tests/kis_shape_selection_test.cpp b/libs/ui/tests/kis_shape_selection_test.cpp index c5beb44036..013136010a 100644 --- a/libs/ui/tests/kis_shape_selection_test.cpp +++ b/libs/ui/tests/kis_shape_selection_test.cpp @@ -1,78 +1,81 @@ /* * Copyright (c) 2008 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection_test.h" #include #include #include #include #include #include #include "kis_selection.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "kis_image.h" #include "testutil.h" void KisShapeSelectionTest::testAddChild() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 300, 300, cs, "test"); KisSelectionSP selection = new KisSelection(); QVERIFY(selection->hasPixelSelection() == false); QVERIFY(selection->hasShapeSelection() == false); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); + QRect rect(50, 50, 100, 100); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); rect = matrix.mapRect(rect); KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topLeft() + QPointF(rect.width(), 0)); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.topLeft() + QPointF(0, rect.height())); shape->close(); shape->normalize(); KisShapeSelection * shapeSelection = new KisShapeSelection(image, selection); selection->setShapeSelection(shapeSelection); shapeSelection->addShape(shape); + selection->updateProjection(); + QTest::qWait(500); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); - selection->updateProjection(); + } QTEST_MAIN(KisShapeSelectionTest) diff --git a/libs/ui/tool/KisStrokeSpeedMonitor.cpp b/libs/ui/tool/KisStrokeSpeedMonitor.cpp index 99cdce929a..e7c19e2e69 100644 --- a/libs/ui/tool/KisStrokeSpeedMonitor.cpp +++ b/libs/ui/tool/KisStrokeSpeedMonitor.cpp @@ -1,210 +1,210 @@ /* * 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 "KisStrokeSpeedMonitor.h" #include #include #include #include #include "kis_paintop_preset.h" #include "kis_paintop_settings.h" #include "kis_config.h" #include "kis_config_notifier.h" -#include "KisUpdateSchedulerConfigNotifier.h" +#include "KisImageConfigNotifier.h" Q_GLOBAL_STATIC(KisStrokeSpeedMonitor, s_instance) struct KisStrokeSpeedMonitor::Private { static const int averageWindow = 10; Private() : avgCursorSpeed(averageWindow), avgRenderingSpeed(averageWindow), avgFps(averageWindow) { } KisRollingMeanAccumulatorWrapper avgCursorSpeed; KisRollingMeanAccumulatorWrapper avgRenderingSpeed; KisRollingMeanAccumulatorWrapper avgFps; qreal cachedAvgCursorSpeed = 0; qreal cachedAvgRenderingSpeed = 0; qreal cachedAvgFps = 0; qreal lastCursorSpeed = 0; qreal lastRenderingSpeed = 0; qreal lastFps = 0; bool lastStrokeSaturated = false; QByteArray lastPresetMd5; QString lastPresetName; qreal lastPresetSize = 0; bool haveStrokeSpeedMeasurement = true; QMutex mutex; }; KisStrokeSpeedMonitor::KisStrokeSpeedMonitor() : m_d(new Private()) { - connect(KisUpdateSchedulerConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetAccumulatedValues())); - connect(KisUpdateSchedulerConfigNotifier::instance(), SIGNAL(configChanged()), SIGNAL(sigStatsUpdated())); + connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetAccumulatedValues())); + connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SIGNAL(sigStatsUpdated())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); } KisStrokeSpeedMonitor::~KisStrokeSpeedMonitor() { } KisStrokeSpeedMonitor *KisStrokeSpeedMonitor::instance() { return s_instance; } bool KisStrokeSpeedMonitor::haveStrokeSpeedMeasurement() const { return m_d->haveStrokeSpeedMeasurement; } void KisStrokeSpeedMonitor::setHaveStrokeSpeedMeasurement(bool value) { m_d->haveStrokeSpeedMeasurement = value; } void KisStrokeSpeedMonitor::resetAccumulatedValues() { m_d->avgCursorSpeed.reset(m_d->averageWindow); m_d->avgRenderingSpeed.reset(m_d->averageWindow); m_d->avgFps.reset(m_d->averageWindow); } void KisStrokeSpeedMonitor::slotConfigChanged() { KisConfig cfg(true); m_d->haveStrokeSpeedMeasurement = cfg.enableBrushSpeedLogging(); resetAccumulatedValues(); emit sigStatsUpdated(); } void KisStrokeSpeedMonitor::notifyStrokeFinished(qreal cursorSpeed, qreal renderingSpeed, qreal fps, KisPaintOpPresetSP preset) { if (qFuzzyCompare(cursorSpeed, 0.0) || qFuzzyCompare(renderingSpeed, 0.0)) return; QMutexLocker locker(&m_d->mutex); const bool isSamePreset = m_d->lastPresetName == preset->name() && qFuzzyCompare(m_d->lastPresetSize, preset->settings()->paintOpSize()); ENTER_FUNCTION() << ppVar(isSamePreset); if (!isSamePreset) { resetAccumulatedValues(); m_d->lastPresetName = preset->name(); m_d->lastPresetSize = preset->settings()->paintOpSize(); } m_d->lastCursorSpeed = cursorSpeed; m_d->lastRenderingSpeed = renderingSpeed; m_d->lastFps = fps; static const qreal saturationSpeedThreshold = 0.30; // cursor speed should be at least 30% higher m_d->lastStrokeSaturated = cursorSpeed / renderingSpeed > (1.0 + saturationSpeedThreshold); if (m_d->lastStrokeSaturated) { m_d->avgCursorSpeed(cursorSpeed); m_d->avgRenderingSpeed(renderingSpeed); m_d->avgFps(fps); m_d->cachedAvgCursorSpeed = m_d->avgCursorSpeed.rollingMean(); m_d->cachedAvgRenderingSpeed = m_d->avgRenderingSpeed.rollingMean(); m_d->cachedAvgFps = m_d->avgFps.rollingMean(); } emit sigStatsUpdated(); ENTER_FUNCTION() << QString(" CS: %1 RS: %2 FPS: %3 %4") .arg(m_d->lastCursorSpeed, 5) .arg(m_d->lastRenderingSpeed, 5) .arg(m_d->lastFps, 5) .arg(m_d->lastStrokeSaturated ? "(saturated)" : ""); ENTER_FUNCTION() << QString("ACS: %1 ARS: %2 AFPS: %3") .arg(m_d->cachedAvgCursorSpeed, 5) .arg(m_d->cachedAvgRenderingSpeed, 5) .arg(m_d->cachedAvgFps, 5); } QString KisStrokeSpeedMonitor::lastPresetName() const { return m_d->lastPresetName; } qreal KisStrokeSpeedMonitor::lastPresetSize() const { return m_d->lastPresetSize; } qreal KisStrokeSpeedMonitor::lastCursorSpeed() const { return m_d->lastCursorSpeed; } qreal KisStrokeSpeedMonitor::lastRenderingSpeed() const { return m_d->lastRenderingSpeed; } qreal KisStrokeSpeedMonitor::lastFps() const { return m_d->lastFps; } bool KisStrokeSpeedMonitor::lastStrokeSaturated() const { return m_d->lastStrokeSaturated; } qreal KisStrokeSpeedMonitor::avgCursorSpeed() const { return m_d->cachedAvgCursorSpeed; } qreal KisStrokeSpeedMonitor::avgRenderingSpeed() const { return m_d->cachedAvgRenderingSpeed; } qreal KisStrokeSpeedMonitor::avgFps() const { return m_d->cachedAvgFps; } diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 99384963f9..04dcc51baa 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,410 +1,412 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPattern *currentPattern = 0; KoAbstractGradient *currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-runnign actions * will have correct brush parameters. Theoretically this cloniong * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->clone(); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { - return m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp(); + return m_d->currentPaintOpPreset ? + m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() + : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->selectionOverride) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return m_d->currentPaintOpPreset->settings()->isAirbrushing(); } qreal KisResourcesSnapshot::airbrushingInterval() const { return m_d->currentPaintOpPreset->settings()->airbrushInterval(); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return m_d->currentPaintOpPreset->settings()->useSpacingUpdates(); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPattern* KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush; } diff --git a/libs/widgets/tests/CMakeLists.txt b/libs/widgets/tests/CMakeLists.txt index 23fa615c8b..e9e3ed185c 100644 --- a/libs/widgets/tests/CMakeLists.txt +++ b/libs/widgets/tests/CMakeLists.txt @@ -1,31 +1,19 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") #add_subdirectory(filedialogtester) include(ECMAddTests) include(KritaAddBrokenUnitTest) ecm_add_tests( zoomhandler_test.cpp zoomcontroller_test.cpp squeezedcombobox_test.cpp - NAME_PREFIX "libs-widgets-" - LINK_LIBRARIES kritawidgets Qt5::Test) - -krita_add_broken_unit_test( KoResourceTaggingTest.cpp - TEST_NAME libs-widgets-KoResourceTaggingTest - LINK_LIBRARIES kritawidgets Qt5::Test) - -ecm_add_tests( kis_parse_spin_boxes_test.cpp - NAME_PREFIX "krita-ui-" - LINK_LIBRARIES kritaui Qt5::Test) - -ecm_add_tests( KoAnchorSelectionWidgetTest.cpp NAME_PREFIX "libs-widgets-" - LINK_LIBRARIES kritaui Qt5::Test) + LINK_LIBRARIES kritawidgets Qt5::Test) diff --git a/libs/widgets/tests/KoResourceTaggingTest.cpp b/libs/widgets/tests/KoResourceTaggingTest.cpp index df97140c96..820f5429af 100644 --- a/libs/widgets/tests/KoResourceTaggingTest.cpp +++ b/libs/widgets/tests/KoResourceTaggingTest.cpp @@ -1,144 +1,146 @@ /* * 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 "KoResourceTaggingTest.h" #include #include #include #include #include #include "KoResourceServerProvider.h" +#include "sdk/tests/kistest.h" + void KoResourceTaggingTest::testInitialization() { KoResourceTagStore tagStore(KoResourceServerProvider::instance()->patternServer()); QVERIFY(tagStore.tagNamesList().isEmpty()); QVERIFY(tagStore.assignedTagsList(0).isEmpty()); QVERIFY(tagStore.searchTag("bla").isEmpty()); } void KoResourceTaggingTest::testTagging() { KoResourceServer* patServer = KoResourceServerProvider::instance()->patternServer(); KoResourceTagStore tagStore(patServer); KoResource *res = patServer->resources().first(); QVERIFY(res); QVERIFY(patServer->resourceByFilename(res->shortFilename()) == res); tagStore.addTag(res, "test1"); QVERIFY(tagStore.tagNamesList().size() == 1); QStringList resources = tagStore.searchTag("test1"); QVERIFY(resources.size() == 1); KoResource *res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.addTag(res, "test2"); QVERIFY(tagStore.tagNamesList().size() == 2); resources = tagStore.searchTag("test1"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.addTag(res, "test2"); QVERIFY(tagStore.tagNamesList().size() == 2); resources = tagStore.searchTag("test2"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); resources = tagStore.searchTag("test1,test2"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.delTag(res, "test1"); QVERIFY(tagStore.tagNamesList().size() == 2); resources = tagStore.searchTag("test1"); QVERIFY(resources.size() == 0); resources = tagStore.searchTag("test2"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.delTag("test1"); QVERIFY(tagStore.tagNamesList().size() == 1); } void KoResourceTaggingTest::testReadWriteXML() { KoResourceServer* patServer = KoResourceServerProvider::instance()->patternServer(); KoResourceTagStore tagStore(patServer); QList patterns = patServer->resources(); Q_ASSERT(patterns.size() > 5); tagStore.addTag(patterns[0], "test0"); tagStore.addTag(patterns[1], "test1"); tagStore.addTag(patterns[2], "test2"); tagStore.addTag(patterns[2], "test2"); tagStore.addTag(patterns[2], "test1"); tagStore.addTag(patterns[3], "test3"); tagStore.addTag(patterns[4], "test4"); tagStore.addTag(patterns[5], "test5"); tagStore.addTag(patterns[5], "test5.1"); tagStore.addTag(patterns[5], "test5.2"); tagStore.addTag(0, "dummytest"); QVERIFY(tagStore.tagNamesList().size() == 9); tagStore.writeXMLFile(QString(FILES_OUTPUT_DIR) + "/" + "kis_pattern_tags.xml"); KoResourceTagStore tagStore2(patServer); tagStore2.readXMLFile(QString(FILES_OUTPUT_DIR) + "/" + "kis_pattern_tags.xml"); QVERIFY(tagStore2.tagNamesList().size() == 9); QStringList resources = tagStore2.searchTag("test0"); QVERIFY(resources.size() == 1); QVERIFY(patServer->resourceByFilename(resources[0]) == patterns[0]); resources = tagStore2.searchTag("test1"); QVERIFY(resources.size() == 2); resources = tagStore2.searchTag("test2"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test3"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test4"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test5"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test5.1"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test5.2"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("dummytest"); QVERIFY(resources.size() == 0); } -QTEST_MAIN(KoResourceTaggingTest) +KISTEST_MAIN(KoResourceTaggingTest) diff --git a/libs/widgetutils/KoResourcePaths.cpp b/libs/widgetutils/KoResourcePaths.cpp index 1ea1d30d27..a1d677a207 100644 --- a/libs/widgetutils/KoResourcePaths.cpp +++ b/libs/widgetutils/KoResourcePaths.cpp @@ -1,564 +1,586 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoResourcePaths.h" #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" -#include "WidgetUtilsDebug.h" + Q_GLOBAL_STATIC(KoResourcePaths, s_instance); static QString cleanup(const QString &path) { return QDir::cleanPath(path); } static QStringList cleanup(const QStringList &pathList) { QStringList cleanedPathList; Q_FOREACH(const QString &path, pathList) { cleanedPathList << cleanup(path); } return cleanedPathList; } static QString cleanupDirs(const QString &path) { return QDir::cleanPath(path) + QDir::separator(); } static QStringList cleanupDirs(const QStringList &pathList) { QStringList cleanedPathList; Q_FOREACH(const QString &path, pathList) { cleanedPathList << cleanupDirs(path); } return cleanedPathList; } void appendResources(QStringList *dst, const QStringList &src, bool eliminateDuplicates) { Q_FOREACH (const QString &resource, src) { QString realPath = QDir::cleanPath(resource); if (!eliminateDuplicates || !dst->contains(realPath)) { *dst << realPath; } } } #ifdef Q_OS_WIN static const Qt::CaseSensitivity cs = Qt::CaseInsensitive; #else static const Qt::CaseSensitivity cs = Qt::CaseSensitive; #endif #ifdef Q_OS_OSX #include #include #include #endif QString getInstallationPrefix() { #ifdef Q_OS_OSX QString appPath = qApp->applicationDirPath(); - debugWidgetUtils << "1" << appPath; + dbgResources << "1" << appPath; appPath.chop(QString("MacOS/").length()); - debugWidgetUtils << "2" << appPath; + dbgResources << "2" << appPath; bool makeInstall = QDir(appPath + "/../../../share/kritaplugins").exists(); bool inBundle = QDir(appPath + "/Resources/kritaplugins").exists(); - debugWidgetUtils << "3. After make install" << makeInstall; - debugWidgetUtils << "4. In Bundle" << inBundle; + dbgResources << "3. After make install" << makeInstall; + dbgResources << "4. In Bundle" << inBundle; QString bundlePath; if (inBundle) { bundlePath = appPath + "/"; } else if (makeInstall) { appPath.chop(QString("Contents/").length()); bundlePath = appPath + "/../../"; } else { qFatal("Cannot calculate the bundle path from the app path"); } - debugWidgetUtils << ">>>>>>>>>>>" << bundlePath; + dbgResources << ">>>>>>>>>>>" << bundlePath; return bundlePath; #else #ifdef Q_OS_QWIN QDir appdir(qApp->applicationDirPath()); // Corrects for mismatched case errors in path (qtdeclarative fails to load) wchar_t buffer[1024]; QString absolute = appdir.absolutePath(); DWORD rv = ::GetShortPathName((wchar_t*)absolute.utf16(), buffer, 1024); rv = ::GetLongPathName(buffer, buffer, 1024); QString correctedPath((QChar *)buffer); appdir.setPath(correctedPath); appdir.cdUp(); return appdir.canonicalPath(); #else return qApp->applicationDirPath() + "/../"; #endif #endif } class Q_DECL_HIDDEN KoResourcePaths::Private { public: QMap absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global QMap relatives; // Same with relative paths QMutex relativesMutex; QMutex absolutesMutex; - bool ready = false; // Paths have been initialized - QStringList aliases(const QString &type) { QStringList r; QStringList a; relativesMutex.lock(); if (relatives.contains(type)) { r += relatives[type]; } relativesMutex.unlock(); - debugWidgetUtils << "\trelatives" << r; + dbgResources << "\trelatives" << r; absolutesMutex.lock(); if (absolutes.contains(type)) { a += absolutes[type]; } - debugWidgetUtils << "\tabsolutes" << a; + dbgResources << "\tabsolutes" << a; absolutesMutex.unlock(); return r + a; } QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type) { if (type == "tmp") { return QStandardPaths::TempLocation; } else if (type == "appdata") { return QStandardPaths::AppDataLocation; } else if (type == "data") { return QStandardPaths::AppDataLocation; } else if (type == "cache") { return QStandardPaths::CacheLocation; } else if (type == "locale") { return QStandardPaths::AppDataLocation; } else { return QStandardPaths::AppDataLocation; } } }; KoResourcePaths::KoResourcePaths() : d(new Private) { } KoResourcePaths::~KoResourcePaths() { } QString KoResourcePaths::getApplicationRoot() { return getInstallationPrefix(); } void KoResourcePaths::addResourceType(const char *type, const char *basetype, const QString &relativeName, bool priority) { s_instance->addResourceTypeInternal(QString::fromLatin1(type), QString::fromLatin1(basetype), relativeName, priority); } void KoResourcePaths::addResourceDir(const char *type, const QString &dir, bool priority) { s_instance->addResourceDirInternal(QString::fromLatin1(type), dir, priority); } QString KoResourcePaths::findResource(const char *type, const QString &fileName) { return cleanup(s_instance->findResourceInternal(QString::fromLatin1(type), fileName)); } QStringList KoResourcePaths::findDirs(const char *type) { return cleanupDirs(s_instance->findDirsInternal(QString::fromLatin1(type))); } QStringList KoResourcePaths::findAllResources(const char *type, const QString &filter, SearchOptions options) { return cleanup(s_instance->findAllResourcesInternal(QString::fromLatin1(type), filter, options)); } QStringList KoResourcePaths::resourceDirs(const char *type) { return cleanupDirs(s_instance->resourceDirsInternal(QString::fromLatin1(type))); } QString KoResourcePaths::saveLocation(const char *type, const QString &suffix, bool create) { return cleanupDirs(s_instance->saveLocationInternal(QString::fromLatin1(type), suffix, create)); } QString KoResourcePaths::locate(const char *type, const QString &filename) { return cleanup(s_instance->locateInternal(QString::fromLatin1(type), filename)); } QString KoResourcePaths::locateLocal(const char *type, const QString &filename, bool createDir) { return cleanup(s_instance->locateLocalInternal(QString::fromLatin1(type), filename, createDir)); } void KoResourcePaths::addResourceTypeInternal(const QString &type, const QString &basetype, const QString &relativename, bool priority) { Q_UNUSED(basetype); if (relativename.isEmpty()) return; QString copy = relativename; Q_ASSERT(basetype == "data"); if (!copy.endsWith(QLatin1Char('/'))) { copy += QLatin1Char('/'); } d->relativesMutex.lock(); QStringList &rels = d->relatives[type]; // find or insert if (!rels.contains(copy, cs)) { if (priority) { rels.prepend(copy); } else { rels.append(copy); } } d->relativesMutex.unlock(); - debugWidgetUtils << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << d->relatives[type]; + dbgResources << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << d->relatives[type]; } void KoResourcePaths::addResourceDirInternal(const QString &type, const QString &absdir, bool priority) { if (absdir.isEmpty() || type.isEmpty()) return; // find or insert entry in the map QString copy = absdir; if (copy.at(copy.length() - 1) != QLatin1Char('/')) { copy += QLatin1Char('/'); } d->absolutesMutex.lock(); QStringList &paths = d->absolutes[type]; if (!paths.contains(copy, cs)) { if (priority) { paths.prepend(copy); } else { paths.append(copy); } } d->absolutesMutex.unlock(); - debugWidgetUtils << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << d->absolutes[type]; + dbgResources << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << d->absolutes[type]; } QString KoResourcePaths::findResourceInternal(const QString &type, const QString &fileName) { QStringList aliases = d->aliases(type); - debugWidgetUtils << "aliases" << aliases << getApplicationRoot(); + aliases << ""; + dbgResources<< "aliases" << aliases << getApplicationRoot(); QString resource = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName, QStandardPaths::LocateFile); if (resource.isEmpty()) { Q_FOREACH (const QString &alias, aliases) { resource = QStandardPaths::locate(d->mapTypeToQStandardPaths(type), alias + '/' + fileName, QStandardPaths::LocateFile); - debugWidgetUtils << "\t1" << resource; + dbgResources << "\t1" << resource; if (QFile::exists(resource)) { continue; } } } if (resource.isEmpty() || !QFile::exists(resource)) { QString approot = getApplicationRoot(); Q_FOREACH (const QString &alias, aliases) { resource = approot + "/share/" + alias + '/' + fileName; - debugWidgetUtils << "\t1" << resource; + dbgResources << "\t2" << resource; if (QFile::exists(resource)) { continue; } } } if (resource.isEmpty() || !QFile::exists(resource)) { QString approot = getApplicationRoot(); Q_FOREACH (const QString &alias, aliases) { resource = approot + "/share/krita/" + alias + '/' + fileName; - debugWidgetUtils << "\t1" << resource; + dbgResources << "\t3" << resource; if (QFile::exists(resource)) { continue; } } } - debugWidgetUtils << "findResource: type" << type << "filename" << fileName << "resource" << resource; + if (resource.isEmpty() || !QFile::exists(resource)) { + QString extraResourceDirs = qgetenv("EXTRA_RESOURCE_DIRS"); + dbgResources<< ">>>>>>>>>>>>>>>>" << extraResourceDirs << aliases << fileName; + if (!extraResourceDirs.isEmpty()) { + Q_FOREACH(const QString &extraResourceDir, extraResourceDirs.split(':', QString::SkipEmptyParts)) { + Q_FOREACH (const QString &alias, aliases) { + resource = extraResourceDir + '/' + alias + '/' + fileName; + dbgResources<< "\t4" << resource; + if (QFile::exists(resource)) { + continue; + } + } + } + } + } + + + dbgResources<< "findResource: type" << type << "filename" << fileName << "resource" << resource; Q_ASSERT(!resource.isEmpty()); return resource; } + +QStringList filesInDir(const QString &startdir, const QString & filter, bool recursive) +{ + dbgResources << "filesInDir: startdir" << startdir << "filter" << filter << "recursive" << recursive; + QStringList result; + + // First the entries in this path + QStringList nameFilters; + nameFilters << filter; + const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name); + dbgResources << "\tFound:" << fileNames.size() << ":" << fileNames; + Q_FOREACH (const QString &fileName, fileNames) { + QString file = startdir + '/' + fileName; + result << file; + } + + // And then everything underneath, if recursive is specified + if (recursive) { + const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot); + Q_FOREACH (const QString &subdir, entries) { + dbgResources << "\tGoing to look in subdir" << subdir << "of" << startdir; + result << filesInDir(startdir + '/' + subdir, filter, recursive); + } + } + return result; +} + QStringList KoResourcePaths::findDirsInternal(const QString &type) { QStringList aliases = d->aliases(type); - debugWidgetUtils << type << aliases << d->mapTypeToQStandardPaths(type); + dbgResources << type << aliases << d->mapTypeToQStandardPaths(type); QStringList dirs; QStringList standardDirs = QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), "", QStandardPaths::LocateDirectory); appendResources(&dirs, standardDirs, true); Q_FOREACH (const QString &alias, aliases) { QStringList aliasDirs = - QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory); + QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory); appendResources(&dirs, aliasDirs, true); #ifdef Q_OS_OSX - debugWidgetUtils << "MAC:" << getApplicationRoot(); + dbgResources << "MAC:" << getApplicationRoot(); QStringList bundlePaths; bundlePaths << getApplicationRoot() + "/share/krita/" + alias; bundlePaths << getApplicationRoot() + "/../share/krita/" + alias; - debugWidgetUtils << "bundlePaths" << bundlePaths; + dbgResources << "bundlePaths" << bundlePaths; appendResources(&dirs, bundlePaths, true); - Q_ASSERT(!dirs.isEmpty()); + Q_ASSERT(!dirs.isEmpty()); #endif QStringList fallbackPaths; fallbackPaths << getApplicationRoot() + "/share/" + alias; fallbackPaths << getApplicationRoot() + "/share/krita/" + alias; appendResources(&dirs, fallbackPaths, true); } - debugWidgetUtils << "findDirs: type" << type << "resource" << dirs; + dbgResources << "findDirs: type" << type << "resource" << dirs; return dirs; } -QStringList filesInDir(const QString &startdir, const QString & filter, bool recursive) -{ - debugWidgetUtils << "filesInDir: startdir" << startdir << "filter" << filter << "recursive" << recursive; - QStringList result; - - // First the entries in this path - QStringList nameFilters; - nameFilters << filter; - const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name); - debugWidgetUtils << "\tFound:" << fileNames.size() << ":" << fileNames; - Q_FOREACH (const QString &fileName, fileNames) { - QString file = startdir + '/' + fileName; - result << file; - } - - // And then everything underneath, if recursive is specified - if (recursive) { - const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot); - Q_FOREACH (const QString &subdir, entries) { - debugWidgetUtils << "\tGoing to look in subdir" << subdir << "of" << startdir; - result << filesInDir(startdir + '/' + subdir, filter, recursive); - } - } - return result; -} - QStringList KoResourcePaths::findAllResourcesInternal(const QString &type, const QString &_filter, SearchOptions options) const { - debugWidgetUtils << "====================================================="; - debugWidgetUtils << type << _filter << QStandardPaths::standardLocations(d->mapTypeToQStandardPaths(type)); + dbgResources << "====================================================="; + dbgResources << type << _filter << QStandardPaths::standardLocations(d->mapTypeToQStandardPaths(type)); bool recursive = options & KoResourcePaths::Recursive; - debugWidgetUtils << "findAllResources: type" << type << "filter" << _filter << "recursive" << recursive; + dbgResources << "findAllResources: type" << type << "filter" << _filter << "recursive" << recursive; QStringList aliases = d->aliases(type); QString filter = _filter; // In cases where the filter is like "color-schemes/*.colors" instead of "*.kpp", used with unregistered resource types if (filter.indexOf('*') > 0) { aliases << filter.split('*').first(); filter = '*' + filter.split('*')[1]; - debugWidgetUtils << "Split up alias" << aliases << "filter" << filter; + dbgResources << "Split up alias" << aliases << "filter" << filter; } QStringList resources; if (aliases.isEmpty()) { QStringList standardResources = QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), filter, QStandardPaths::LocateFile); + dbgResources << "standardResources" << standardResources; appendResources(&resources, standardResources, true); + dbgResources << "1" << resources; + } + + + QString extraResourceDirs = qgetenv("EXTRA_RESOURCE_DIRS"); + dbgResources << ">>>>>>>>>>>>>>>> extraResourceDirs" << extraResourceDirs; + if (!extraResourceDirs.isEmpty()) { + Q_FOREACH(const QString &extraResourceDir, extraResourceDirs.split(':', QString::SkipEmptyParts)) { + appendResources(&resources, filesInDir(extraResourceDir, filter, recursive), true); + Q_FOREACH (const QString &alias, aliases) { + appendResources(&resources, filesInDir(extraResourceDir + '/' + alias + '/', filter, recursive), true); + } + } + } - debugWidgetUtils << "\tresources from qstandardpaths:" << resources.size(); + dbgResources << "\tresources from qstandardpaths:" << resources.size(); Q_FOREACH (const QString &alias, aliases) { - debugWidgetUtils << "\t\talias:" << alias; + dbgResources << "\t\talias:" << alias; QStringList dirs; QFileInfo dirInfo(alias); if (dirInfo.exists() && dirInfo.isDir() && dirInfo.isAbsolute()) { dirs << alias; } else { dirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory) << getInstallationPrefix() + "share/" + alias + "/" << getInstallationPrefix() + "share/krita/" + alias + "/"; } Q_FOREACH (const QString &dir, dirs) { appendResources(&resources, filesInDir(dir, filter, recursive), true); } } - debugWidgetUtils << "\tresources also from aliases:" << resources.size(); + dbgResources << "\tresources also from aliases:" << resources.size(); // if the original filter is "input/*", we only want share/input/* and share/krita/input/* here, but not // share/*. therefore, use _filter here instead of filter which was split into alias and "*". QFileInfo fi(_filter); QStringList prefixResources; prefixResources << filesInDir(getInstallationPrefix() + "share/" + fi.path(), fi.fileName(), false); prefixResources << filesInDir(getInstallationPrefix() + "share/krita/" + fi.path(), fi.fileName(), false); appendResources(&resources, prefixResources, true); - debugWidgetUtils << "\tresources from installation:" << resources.size(); - debugWidgetUtils << "====================================================="; + dbgResources << "\tresources from installation:" << resources.size(); + dbgResources << "====================================================="; return resources; } QStringList KoResourcePaths::resourceDirsInternal(const QString &type) { QStringList resourceDirs; QStringList aliases = d->aliases(type); Q_FOREACH (const QString &alias, aliases) { QStringList aliasDirs; aliasDirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory); aliasDirs << getInstallationPrefix() + "share/" + alias + "/" << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory); aliasDirs << getInstallationPrefix() + "share/krita/" + alias + "/" << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory); appendResources(&resourceDirs, aliasDirs, true); } - debugWidgetUtils << "resourceDirs: type" << type << resourceDirs; + dbgResources << "resourceDirs: type" << type << resourceDirs; return resourceDirs; } QString KoResourcePaths::saveLocationInternal(const QString &type, const QString &suffix, bool create) { QStringList aliases = d->aliases(type); QString path; if (aliases.size() > 0) { path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type)) + '/' + aliases.first(); } else { path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type)); if (!path.endsWith("krita")) { path += "/krita"; } if (!suffix.isEmpty()) { path += "/" + suffix; } } QDir d(path); if (!d.exists() && create) { d.mkpath(path); } - debugWidgetUtils << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path; + dbgResources << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path; return path; } QString KoResourcePaths::locateInternal(const QString &type, const QString &filename) { QStringList aliases = d->aliases(type); QStringList locations; if (aliases.isEmpty()) { locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), filename, QStandardPaths::LocateFile); } Q_FOREACH (const QString &alias, aliases) { locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), (alias.endsWith('/') ? alias : alias + '/') + filename, QStandardPaths::LocateFile); } - debugWidgetUtils << "locate: type" << type << "filename" << filename << "locations" << locations; + dbgResources << "locate: type" << type << "filename" << filename << "locations" << locations; if (locations.size() > 0) { return locations.first(); } else { return ""; } } QString KoResourcePaths::locateLocalInternal(const QString &type, const QString &filename, bool createDir) { QString path = saveLocationInternal(type, "", createDir); - debugWidgetUtils << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path; + dbgResources << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path; return path + '/' + filename; } - -void KoResourcePaths::setReady() -{ - s_instance->d->ready = true; -} - -bool KoResourcePaths::isReady() -{ - return s_instance->d->ready; -} diff --git a/libs/widgetutils/KoResourcePaths.h b/libs/widgetutils/KoResourcePaths.h index 853ad89bbc..5dd831396a 100644 --- a/libs/widgetutils/KoResourcePaths.h +++ b/libs/widgetutils/KoResourcePaths.h @@ -1,263 +1,251 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCEPATHS_H #define KORESOURCEPATHS_H #include #include #include #include /** * DEBUGGING KoResourcePaths: * * The usual place to look for resources is Qt's AppDataLocation. * This corresponds to XDG_DATA_DIRS on Linux. To ensure your installation and * path are configured correctly, ensure your files are located in the directories * contained in this variable: * * QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); * * There are many debug lines that can be uncommented for more specific installation * checks. In the future these should be converted to qloggingcategory to enable * convenient enable/disable functionality. */ class KRITAWIDGETUTILS_EXPORT KoResourcePaths { public: KoResourcePaths(); virtual ~KoResourcePaths(); enum SearchOption { NoSearchOptions = 0, Recursive = 1, IgnoreExecBit = 4 }; Q_DECLARE_FLAGS(SearchOptions, SearchOption) static QString getApplicationRoot(); /** * Adds suffixes for types. * * You may add as many as you need, but it is advised that there * is exactly one to make writing definite. * * The later a suffix is added, the higher its priority. Note, that the * suffix should end with / but doesn't have to start with one (as prefixes * should end with one). So adding a suffix for app_pics would look * like KoStandardPaths::addResourceType("app_pics", "data", "app/pics"); * * @param type Specifies a short descriptive string to access * files of this type. * @param basetype Specifies an already known type, or 0 if none * @param relativename Specifies a directory relative to the basetype * @param priority if true, the directory is added before any other, * otherwise after */ static void addResourceType(const char *type, const char *basetype, const QString &relativeName, bool priority = true); /** * Adds absolute path at the beginning of the search path for * particular types (for example in case of icons where * the user specifies extra paths). * * You shouldn't need this function in 99% of all cases besides * adding user-given paths. * * @param type Specifies a short descriptive string to access files * of this type. * @param absdir Points to directory where to look for this specific * type. Non-existent directories may be saved but pruned. * @param priority if true, the directory is added before any other, * otherwise after */ static void addResourceDir(const char *type, const QString &dir, bool priority = true); /** * Tries to find a resource in the following order: * @li All PREFIX/\ paths (most recent first). * @li All absolute paths (most recent first). * * The filename should be a filename relative to the base dir * for resources. So is a way to get the path to libkdecore.la * to findResource("lib", "libkdecore.la"). KStandardDirs will * then look into the subdir lib of all elements of all prefixes * ($KDEDIRS) for a file libkdecore.la and return the path to * the first one it finds (e.g. /opt/kde/lib/libkdecore.la). * * Example: * @code * QString iconfilename = KStandardPaths::findResource("icon",QString("oxygen/22x22/apps/ktip.png")); * @endcode * * @param type The type of the wanted resource * @param filename A relative filename of the resource. * * @return A full path to the filename specified in the second * argument, or QString() if not found. */ static QString findResource(const char *type, const QString &fileName); /** * Tries to find all directories whose names consist of the * specified type and a relative path. So * findDirs("xdgdata-apps", "Settings") would return * @li /home/joe/.local/share/applications/Settings/ * @li /usr/share/applications/Settings/ * * (from the most local to the most global) * * Note that it appends @c / to the end of the directories, * so you can use this right away as directory names. * * @param type The type of the base directory. * @param reldir Relative directory. * * @return A list of matching directories, or an empty * list if the resource specified is not found. */ static QStringList findDirs(const char *type); /** * Tries to find all resources with the specified type. * * The function will look into all specified directories * and return all filenames in these directories. * * The "most local" files are returned before the "more global" files. * * @param type The type of resource to locate directories for. * @param filter Only accept filenames that fit to filter. The filter * may consist of an optional directory and a QRegExp * wildcard expression. E.g. "images\*.jpg". * Use QString() if you do not want a filter. * @param options if the flags passed include Recursive, subdirectories * will also be search. * * @return List of all the files whose filename matches the * specified filter. */ static QStringList findAllResources(const char *type, const QString &filter = QString(), SearchOptions options = NoSearchOptions); /** * @param type The type of resource * @return The list of possible directories for the specified @p type. * The function updates the cache if possible. If the resource * type specified is unknown, it will return an empty list. * Note, that the directories are assured to exist beside the save * location, which may not exist, but is returned anyway. */ static QStringList resourceDirs(const char *type); /** * Finds a location to save files into for the given type * in the user's home directory. * * @param type The type of location to return. * @param suffix A subdirectory name. * Makes it easier for you to create subdirectories. * You can't pass filenames here, you _have_ to pass * directory names only and add possible filename in * that directory yourself. A directory name always has a * trailing slash ('/'). * @param create If set, saveLocation() will create the directories * needed (including those given by @p suffix). * * @return A path where resources of the specified type should be * saved, or QString() if the resource type is unknown. */ static QString saveLocation(const char *type, const QString &suffix = QString(), bool create = true); /** * This function is just for convenience. It simply calls * KoResourcePaths::findResource((type, filename). * * @param type The type of the wanted resource, see KStandardDirs * @param filename A relative filename of the resource * * @return A full path to the filename specified in the second * argument, or QString() if not found **/ static QString locate(const char *type, const QString &filename); /** * This function is much like locate. However it returns a * filename suitable for writing to. No check is made if the * specified @p filename actually exists. Missing directories * are created. If @p filename is only a directory, without a * specific file, @p filename must have a trailing slash. * * @param type The type of the wanted resource, see KStandardDirs * @param filename A relative filename of the resource * * @return A full path to the filename specified in the second * argument, or QString() if not found **/ static QString locateLocal(const char *type, const QString &filename, bool createDir = false); - /** - * Indicate that resource paths have been initialized and users - * of this class may expect to load resources from the proper paths. - */ - static void setReady(); - - /** - * Return if resource paths have been initialized and users - * of this class may expect to load resources from the proper paths. - */ - static bool isReady(); - private: void addResourceTypeInternal(const QString &type, const QString &basetype, const QString &relativeName, bool priority); void addResourceDirInternal(const QString &type, const QString &absdir, bool priority); QString findResourceInternal(const QString &type, const QString &fileName); QStringList findDirsInternal(const QString &type); QStringList findAllResourcesInternal(const QString &type, const QString &filter = QString(), SearchOptions options = NoSearchOptions) const; QStringList resourceDirsInternal(const QString &type); QString saveLocationInternal(const QString &type, const QString &suffix = QString(), bool create = true); QString locateInternal(const QString &type, const QString &filename); QString locateLocalInternal(const QString &type, const QString &filename, bool createDir = false); class Private; QScopedPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KoResourcePaths::SearchOptions) #endif // KORESOURCEPATHS_H diff --git a/plugins/color/lcms2engine/LcmsEnginePlugin.cpp b/plugins/color/lcms2engine/LcmsEnginePlugin.cpp index 0d572b5a56..a2df81c249 100644 --- a/plugins/color/lcms2engine/LcmsEnginePlugin.cpp +++ b/plugins/color/lcms2engine/LcmsEnginePlugin.cpp @@ -1,321 +1,314 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004,2010 Cyrille Berger * Copyright (c) 2011 Srikanth Tiyyagura * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; 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 "LcmsEnginePlugin.h" #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include #include #include #include "IccColorSpaceEngine.h" #include "colorprofiles/LcmsColorProfileContainer.h" #include "colorspaces/cmyk_u8/CmykU8ColorSpace.h" #include "colorspaces/cmyk_u16/CmykU16ColorSpace.h" #include "colorspaces/cmyk_f32/CmykF32ColorSpace.h" #include "colorspaces/gray_u8/GrayU8ColorSpace.h" #include "colorspaces/gray_u16/GrayU16ColorSpace.h" #include "colorspaces/gray_f32/GrayF32ColorSpace.h" #include "colorspaces/lab_u8/LabU8ColorSpace.h" #include "colorspaces/lab_u16/LabColorSpace.h" #include "colorspaces/lab_f32/LabF32ColorSpace.h" #include "colorspaces/xyz_u8/XyzU8ColorSpace.h" #include "colorspaces/xyz_u16/XyzU16ColorSpace.h" #include "colorspaces/xyz_f32/XyzF32ColorSpace.h" #include "colorspaces/rgb_u8/RgbU8ColorSpace.h" #include "colorspaces/rgb_u16/RgbU16ColorSpace.h" #include "colorspaces/rgb_f32/RgbF32ColorSpace.h" #include "colorspaces/ycbcr_u8/YCbCrU8ColorSpace.h" #include "colorspaces/ycbcr_u16/YCbCrU16ColorSpace.h" #include "colorspaces/ycbcr_f32/YCbCrF32ColorSpace.h" #include #ifdef HAVE_OPENEXR # include # ifdef HAVE_LCMS24 # include "colorspaces/gray_f16/GrayF16ColorSpace.h" # include "colorspaces/xyz_f16/XyzF16ColorSpace.h" # include "colorspaces/rgb_f16/RgbF16ColorSpace.h" # endif #endif void lcms2LogErrorHandlerFunction(cmsContext /*ContextID*/, cmsUInt32Number ErrorCode, const char *Text) { qCritical() << "Lcms2 error: " << ErrorCode << Text; } -K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "kolcmsengine.json", - registerPlugin();) +K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "kolcmsengine.json", registerPlugin();) LcmsEnginePlugin::LcmsEnginePlugin(QObject *parent, const QVariantList &) : QObject(parent) { - // We need all resource paths to be properly initialized via KisApplication, otherwise we will - // initialize this instance with lacking color profiles which will cause lookup errors later on. - - KIS_ASSERT_X(KoResourcePaths::isReady() || - (QApplication::instance()->applicationName() != "krita" && - QApplication::instance()->applicationName() != "krita.exe"), - "LcmsEnginePlugin::LcmsEnginePlugin", "Resource paths are not ready yet."); - + KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); + KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); // Set the lmcs error reporting function cmsSetLogErrorHandler(&lcms2LogErrorHandlerFunction); KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance(); // Initialise color engine KoColorSpaceEngineRegistry::instance()->add(new IccColorSpaceEngine); QStringList profileFilenames; profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.icm", KoResourcePaths::Recursive); profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.ICM", KoResourcePaths::Recursive); profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.ICC", KoResourcePaths::Recursive); profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.icc", KoResourcePaths::Recursive); QStringList iccProfileDirs; #ifdef Q_OS_MAC iccProfileDirs.append(QDir::homePath() + "/Library/ColorSync/Profiles/"); iccProfileDirs.append("/System/Library/ColorSync/Profiles/"); iccProfileDirs.append("/Library/ColorSync/Profiles/"); #endif #ifdef Q_OS_WIN QString winPath = QString::fromUtf8(qgetenv("windir")); winPath.replace('\\','/'); iccProfileDirs.append(winPath + "/System32/Spool/Drivers/Color/"); #endif Q_FOREACH(const QString &iccProfiledir, iccProfileDirs) { QDir profileDir(iccProfiledir); Q_FOREACH(const QString &entry, profileDir.entryList(QStringList() << "*.icm" << "*.icc", QDir::NoDotAndDotDot | QDir::Files | QDir::Readable)) { profileFilenames << iccProfiledir + "/" + entry; } } // Load the profiles if (!profileFilenames.empty()) { for (QStringList::Iterator it = profileFilenames.begin(); it != profileFilenames.end(); ++it) { KoColorProfile *profile = new IccColorProfile(*it); Q_CHECK_PTR(profile); profile->load(); if (profile->valid()) { //qDebug() << "Valid profile : " << profile->fileName() << profile->name(); registry->addProfileToMap(profile); } else { qDebug() << "Invalid profile : " << profile->fileName() << profile->name(); delete profile; } } } // ------------------- LAB --------------------------------- KoColorProfile *labProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreateLab2Profile(0)); registry->addProfile(labProfile); registry->add(new LabU8ColorSpaceFactory()); registry->add(new LabU16ColorSpaceFactory()); registry->add(new LabF32ColorSpaceFactory()); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("LABAU8HISTO", i18n("L*a*b*/8 Histogram")), LABAColorModelID.id(), Integer8BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("LABAU16HISTO", i18n("L*a*b*/16 Histogram")), LABAColorModelID.id(), Integer16BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("LABAF32HISTO", i18n("L*a*b*/32 Histogram")), LABAColorModelID.id(), Float32BitsColorDepthID.id())); // ------------------- RGB --------------------------------- KoColorProfile *rgbProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreate_sRGBProfile()); registry->addProfile(rgbProfile); registry->add(new RgbU8ColorSpaceFactory()); registry->add(new RgbU16ColorSpaceFactory()); #ifdef HAVE_LCMS24 #ifdef HAVE_OPENEXR registry->add(new RgbF16ColorSpaceFactory()); #endif #endif registry->add(new RgbF32ColorSpaceFactory()); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("RGBU8HISTO", i18n("RGBA/8 Histogram")), RGBAColorModelID.id(), Integer8BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("RGBU16HISTO", i18n("RGBA/16 Histogram")), RGBAColorModelID.id(), Integer16BitsColorDepthID.id())); #ifdef HAVE_LCMS24 #ifdef HAVE_OPENEXR KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("RGBF16HISTO", i18n("RGBA/F16 Histogram")), RGBAColorModelID.id(), Float16BitsColorDepthID.id())); #endif #endif KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("RGF328HISTO", i18n("RGBA/F32 Histogram")), RGBAColorModelID.id(), Float32BitsColorDepthID.id())); // ------------------- GRAY --------------------------------- cmsToneCurve *Gamma = cmsBuildGamma(0, 2.2); cmsHPROFILE hProfile = cmsCreateGrayProfile(cmsD50_xyY(), Gamma); cmsFreeToneCurve(Gamma); KoColorProfile *defProfile = LcmsColorProfileContainer::createFromLcmsProfile(hProfile); registry->addProfile(defProfile); registry->add(new GrayAU8ColorSpaceFactory()); registry->add(new GrayAU16ColorSpaceFactory()); #ifdef HAVE_LCMS24 #ifdef HAVE_OPENEXR registry->add(new GrayF16ColorSpaceFactory()); #endif #endif registry->add(new GrayF32ColorSpaceFactory()); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("GRAYA8HISTO", i18n("GRAY/8 Histogram")), GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("GRAYA16HISTO", i18n("GRAY/16 Histogram")), GrayAColorModelID.id(), Integer16BitsColorDepthID.id())); #ifdef HAVE_LCMS24 #ifdef HAVE_OPENEXR KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("GRAYF16HISTO", i18n("GRAYF/F16 Histogram")), GrayAColorModelID.id(), Float16BitsColorDepthID.id())); #endif #endif KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("GRAYAF32HISTO", i18n("GRAY/F32 float Histogram")), GrayAColorModelID.id(), Float32BitsColorDepthID.id())); // ------------------- CMYK --------------------------------- registry->add(new CmykU8ColorSpaceFactory()); registry->add(new CmykU16ColorSpaceFactory()); registry->add(new CmykF32ColorSpaceFactory()); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("CMYK8HISTO", i18n("CMYK/8 Histogram")), CMYKAColorModelID.id(), Integer8BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("CMYK16HISTO", i18n("CMYK/16 Histogram")), CMYKAColorModelID.id(), Integer16BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("CMYKF32HISTO", i18n("CMYK/F32 Histogram")), CMYKAColorModelID.id(), Float32BitsColorDepthID.id())); // ------------------- XYZ --------------------------------- KoColorProfile *xyzProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreateXYZProfile()); registry->addProfile(xyzProfile); registry->add(new XyzU8ColorSpaceFactory()); registry->add(new XyzU16ColorSpaceFactory()); #ifdef HAVE_LCMS24 #ifdef HAVE_OPENEXR registry->add(new XyzF16ColorSpaceFactory()); #endif #endif registry->add(new XyzF32ColorSpaceFactory()); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("XYZ8HISTO", i18n("XYZ/8 Histogram")), XYZAColorModelID.id(), Integer8BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("XYZ16HISTO", i18n("XYZ/16 Histogram")), XYZAColorModelID.id(), Integer16BitsColorDepthID.id())); #ifdef HAVE_LCMS24 #ifdef HAVE_OPENEXR KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("XYZF16HISTO", i18n("XYZ/F16 Histogram")), XYZAColorModelID.id(), Float16BitsColorDepthID.id())); #endif #endif KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("XYZF32HISTO", i18n("XYZF32 Histogram")), XYZAColorModelID.id(), Float32BitsColorDepthID.id())); // ------------------- YCBCR --------------------------------- // KoColorProfile *yCbCrProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreateYCBCRProfile()); // registry->addProfile(yCbCrProfile); registry->add(new YCbCrU8ColorSpaceFactory()); registry->add(new YCbCrU16ColorSpaceFactory()); registry->add(new YCbCrF32ColorSpaceFactory()); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("YCBCR8HISTO", i18n("YCbCr/8 Histogram")), YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("YCBCR16HISTO", i18n("YCbCr/16 Histogram")), YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id())); KoHistogramProducerFactoryRegistry::instance()->add( new KoBasicHistogramProducerFactory (KoID("YCBCRF32HISTO", i18n("YCbCr/F32 Histogram")), YCbCrAColorModelID.id(), Float32BitsColorDepthID.id())); // Add profile alias for default profile from lcms1 registry->addProfileAlias("sRGB built-in - (lcms internal)", "sRGB built-in"); registry->addProfileAlias("gray built-in - (lcms internal)", "gray built-in"); registry->addProfileAlias("Lab identity built-in - (lcms internal)", "Lab identity built-in"); registry->addProfileAlias("XYZ built-in - (lcms internal)", "XYZ identity built-in"); } #include diff --git a/plugins/dockers/animation/tests/timeline_model_test.cpp b/plugins/dockers/animation/tests/timeline_model_test.cpp index 5f78968483..6a1456f359 100644 --- a/plugins/dockers/animation/tests/timeline_model_test.cpp +++ b/plugins/dockers/animation/tests/timeline_model_test.cpp @@ -1,298 +1,300 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_model_test.h" #include "kis_image.h" #include "kis_node.h" #include "kis_paint_device.h" #include #include #include #include #include "kis_image_animation_interface.h" #include "KisDocument.h" #include "KisPart.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" #include "kis_undo_adapter.h" #include "timeline_frames_view.h" #include "timeline_frames_model.h" #include "kis_node_dummies_graph.h" #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" +#include + void TimelineModelTest::init() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); m_shapeController = new KisShapeController(m_doc, m_nameServer); //m_nodeModel = new KisNodeModel(0); initBase(); } void TimelineModelTest::cleanup() { cleanupBase(); //delete m_nodeModel; delete m_shapeController; delete m_nameServer; delete m_doc; } #include "timeline_frames_index_converter.h" void TimelineModelTest::testConverter() { constructImage(); addSelectionMasks(); m_shapeController->setImage(m_image); m_layer1->enableAnimation(); m_layer1->setUseInTimeline(true); m_layer2->setUseInTimeline(true); m_sel3->setUseInTimeline(true); TimelineFramesIndexConverter converter(m_shapeController); QCOMPARE(converter.rowCount(), 3); QCOMPARE(converter.rowForDummy(m_shapeController->dummyForNode(m_layer1)), 2); QCOMPARE(converter.rowForDummy(m_shapeController->dummyForNode(m_layer2)), 1); QCOMPARE(converter.rowForDummy(m_shapeController->dummyForNode(m_sel3)), 0); QCOMPARE(converter.dummyFromRow(2), m_shapeController->dummyForNode(m_layer1)); QCOMPARE(converter.dummyFromRow(1), m_shapeController->dummyForNode(m_layer2)); QCOMPARE(converter.dummyFromRow(0), m_shapeController->dummyForNode(m_sel3)); TimelineNodeListKeeper keeper(0, m_shapeController); QCOMPARE(keeper.rowCount(), 3); QCOMPARE(keeper.rowForDummy(m_shapeController->dummyForNode(m_layer1)), 2); QCOMPARE(keeper.rowForDummy(m_shapeController->dummyForNode(m_layer2)), 1); QCOMPARE(keeper.rowForDummy(m_shapeController->dummyForNode(m_sel3)), 0); QCOMPARE(keeper.dummyFromRow(2), m_shapeController->dummyForNode(m_layer1)); QCOMPARE(keeper.dummyFromRow(1), m_shapeController->dummyForNode(m_layer2)); QCOMPARE(keeper.dummyFromRow(0), m_shapeController->dummyForNode(m_sel3)); TimelineNodeListKeeper::OtherLayersList list = keeper.otherLayersList(); Q_FOREACH (const TimelineNodeListKeeper::OtherLayer &l, list) { qDebug() << ppVar(l.name) << ppVar(l.dummy->node()->name()); } } void TimelineModelTest::testModel() { QScopedPointer model(new TimelineFramesModel(0)); } struct TestingInterface : TimelineFramesModel::NodeManipulationInterface { TestingInterface(KisImageSP image) : m_image(image) {} KisLayerSP addPaintLayer() const override { KisNodeSP parent = m_image->root(); KisNodeSP after = parent->lastChild(); KisPaintLayerSP layer = new KisPaintLayer(const_cast(m_image.data()), m_image->nextLayerName(), OPACITY_OPAQUE_U8, m_image->colorSpace()); m_image->undoAdapter()->addCommand( new KisImageLayerAddCommand(m_image, layer, parent, after, false, false)); return layer; } void removeNode(KisNodeSP node) const override { m_image->undoAdapter()->addCommand( new KisImageLayerRemoveCommand(m_image, node)); } private: KisImageSP m_image; }; void TimelineModelTest::testView() { QDialog dlg; QFont font; font.setPointSizeF(9); dlg.setFont(font); QSpinBox *intFps = new KisIntParseSpinBox(&dlg); intFps->setValue(12); QSpinBox *intTime = new KisIntParseSpinBox(&dlg); intTime->setValue(0); intTime->setMaximum(10000); QSpinBox *intLayer = new KisIntParseSpinBox(&dlg); intLayer->setValue(0); intLayer->setMaximum(100); TimelineFramesView *framesTable = new TimelineFramesView(&dlg); TimelineFramesModel *model = new TimelineFramesModel(&dlg); constructImage(); addSelectionMasks(); m_shapeController->setImage(m_image); m_image->animationInterface()->requestTimeSwitchWithUndo(4); framesTable->setModel(model); model->setDummiesFacade(m_shapeController, m_image); model->setNodeManipulationInterface(new TestingInterface(m_image)); m_layer1->enableAnimation(); m_layer1->setUseInTimeline(true); connect(intFps, SIGNAL(valueChanged(int)), m_image->animationInterface(), SLOT(setFramerate(int))); connect(intTime, SIGNAL(valueChanged(int)), SLOT(setCurrentTime(int))); connect(m_image->animationInterface(), SIGNAL(sigUiTimeChanged(int)), intTime, SLOT(setValue(int))); connect(intLayer, SIGNAL(valueChanged(int)), SLOT(setCurrentLayer(int))); connect(this, SIGNAL(sigRequestNodeChange(KisNodeSP)), model, SLOT(slotCurrentNodeChanged(KisNodeSP))); connect(model, SIGNAL(requestCurrentNodeChanged(KisNodeSP)), this, SLOT(slotGuiChangedNode(KisNodeSP))); QVBoxLayout *layout = new QVBoxLayout(&dlg); layout->addWidget(intFps); layout->addWidget(intTime); layout->addWidget(intLayer); layout->addWidget(framesTable); layout->setStretch(0, 0); layout->setStretch(1, 0); layout->setStretch(2, 0); layout->setStretch(3, 1); dlg.resize(600, 400); dlg.exec(); } void TimelineModelTest::setCurrentTime(int time) { m_image->animationInterface()->requestTimeSwitchWithUndo(time); } KisNodeDummy* findNodeFromRowAny(KisNodeDummy *root, int &startCount) { if (!startCount) { return root; } startCount--; KisNodeDummy *dummy = root->lastChild(); while (dummy) { KisNodeDummy *found = findNodeFromRowAny(dummy, startCount); if (found) return found; dummy = dummy->prevSibling(); } return 0; } void TimelineModelTest::setCurrentLayer(int row) { KisNodeDummy *root = m_shapeController->rootDummy(); KisNodeDummy *dummy = findNodeFromRowAny(root, row); if (!dummy) { qDebug() << "WARNING: Cannot find a node at pos" << row; return; } else { qDebug() << "NonGUI changed active node: " << dummy->node()->name(); } emit sigRequestNodeChange(dummy->node()); } void TimelineModelTest::slotGuiChangedNode(KisNodeSP node) { qDebug() << "GUI changed active node:" << node->name(); } #include "kis_equalizer_column.h" #include "kis_equalizer_slider.h" #include "kis_equalizer_widget.h" void TimelineModelTest::testOnionSkins() { QDialog dlg; QFont font; font.setPointSizeF(9); dlg.setFont(font); QHBoxLayout *layout = new QHBoxLayout(&dlg); KisEqualizerWidget *w = new KisEqualizerWidget(10, &dlg); connect(w, SIGNAL(sigConfigChanged()), SLOT(slotBang())); layout->addWidget(w); dlg.setLayout(layout); dlg.resize(600, 400); dlg.exec(); } void TimelineModelTest::slotBang() { ENTER_FUNCTION() << "!!!!"; } -QTEST_MAIN(TimelineModelTest) +KISTEST_MAIN(TimelineModelTest) diff --git a/plugins/extensions/pykrita/CMakeLists.txt b/plugins/extensions/pykrita/CMakeLists.txt index baf3ecbba1..ee9fe363fb 100644 --- a/plugins/extensions/pykrita/CMakeLists.txt +++ b/plugins/extensions/pykrita/CMakeLists.txt @@ -1,11 +1,9 @@ if (HAVE_PYQT5 AND HAVE_SIP AND HAVE_PYTHONLIBS) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SIP_INCLUDE_DIR} ${PYTHON_INCLUDE_PATH}) add_subdirectory(sip) add_subdirectory(plugin) add_subdirectory(kritarunner) - add_test(pythonUnitTests ${PYTHON_EXECUTABLE} -m unittest discover "${CMAKE_INSTALL_PREFIX}" "${CMAKE_SOURCE_DIR}" -s ${CMAKE_SOURCE_DIR}/plugins/extensions/pykrita/tests -p "*_test.py") - endif () diff --git a/plugins/extensions/qmic/PluginSettings.cpp b/plugins/extensions/qmic/PluginSettings.cpp index fb520a9ac9..1f3287d8fb 100644 --- a/plugins/extensions/qmic/PluginSettings.cpp +++ b/plugins/extensions/qmic/PluginSettings.cpp @@ -1,119 +1,119 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PluginSettings.h" #include #include #include #include #include #include #include #include "kis_config.h" PluginSettings::PluginSettings(QWidget *parent) : KisPreferenceSet(parent) { setupUi(this); fileRequester->setFileName(gmicQtPath()); fileRequester->setConfigurationName("gmic_qt"); fileRequester->setStartDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); } PluginSettings::~PluginSettings() { KisConfig(false).writeEntry("gmic_qt_plugin_path", fileRequester->fileName()); } QString PluginSettings::id() { return QString("qmicsettings"); } QString PluginSettings::name() { return header(); } QString PluginSettings::header() { return QString(i18n("G'Mic-Qt Integration")); } QIcon PluginSettings::icon() { return koIcon("gmic"); } QString PluginSettings::gmicQtPath() { QString gmicqt = "gmic_krita_qt"; #ifdef Q_OS_WIN gmicqt += ".exe"; #endif QString gmic_qt_path = KisConfig(true).readEntry("gmic_qt_plugin_path", ""); if (!gmic_qt_path.isEmpty() && QFileInfo(gmic_qt_path).exists()) { return gmic_qt_path; } QFileInfo fi(qApp->applicationDirPath() + "/" + gmicqt); // Check for gmic-qt next to krita if (fi.exists() && fi.isFile()) { -// qDebug() << 1 << fi.canonicalFilePath(); +// dbgPlugins << 1 << fi.canonicalFilePath(); return fi.canonicalFilePath(); } // Check whether we've got a gmic subfolder QDir d(qApp->applicationDirPath()); QStringList gmicdirs = d.entryList(QStringList() << "gmic*", QDir::Dirs); - qDebug() << gmicdirs; + dbgPlugins << gmicdirs; if (gmicdirs.isEmpty()) { -// qDebug() << 2; +// dbgPlugins << 2; return ""; } fi = QFileInfo(qApp->applicationDirPath() + "/" + gmicdirs.first() + "/" + gmicqt); if (fi.exists() && fi.isFile()) { -// qDebug() << "3" << fi.canonicalFilePath(); +// dbgPlugins << "3" << fi.canonicalFilePath(); return fi.canonicalFilePath(); } -// qDebug() << 4 << gmicqt; +// dbgPlugins << 4 << gmicqt; return gmicqt; } void PluginSettings::savePreferences() const { KisConfig(false).writeEntry("gmic_qt_plugin_path", fileRequester->fileName()); Q_EMIT(settingsChanged()); } void PluginSettings::loadPreferences() { fileRequester->setFileName(gmicQtPath()); } void PluginSettings::loadDefaultPreferences() { fileRequester->setFileName(gmicQtPath()); } diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index 23dd4c6bf8..5e292c7145 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,464 +1,464 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisActionPlugin(parent) , m_gmicApplicator(0) { #ifndef Q_OS_MAC KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); #endif } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { -// qDebug() << "detaching" << memorySegment->key(); +// dbgPlugins << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); // find the krita-gmic-qt plugin QString pluginPath = PluginSettings::gmicQtPath(); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists() || !QFileInfo(pluginPath).isFile()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Krita cannot find the gmic-qt plugin. You can set the location of the gmic-qt plugin in Settings/Configure Krita.")); return; } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); connect(viewManager(), SIGNAL(destroyed(QObject *o)), m_pluginProcess, SLOT(terminate())); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString())); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } - qDebug() << "Plugin started" << r << m_pluginProcess->state(); + dbgPlugins << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { - qDebug() << "connected"; + dbgPlugins << "connected"; if (!viewManager()) return; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); // FIXME: Should use read transaction for Qt >= 5.7: // https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); - qDebug() << "Received" << message; + dbgPlugins << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; QString messageBoxWarningText; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = viewManager()->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(viewManager()->image()->width()) + "," + QByteArray::number(viewManager()->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack - qDebug() << "gmic_qt_output_images"; + dbgPlugins << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { messageBoxWarningText = i18n("Sorry, this output mode is not implemented yet."); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { - qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached(); + dbgPlugins << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { - qDebug() << "\t" << memorySegment->error() << memorySegment->errorString(); + dbgPlugins << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } - qDebug() << "Sending" << QString::fromUtf8(ba); + dbgPlugins << "Sending" << QString::fromUtf8(ba); // HACK: Make sure QDataStream does not refuse to write! // Proper fix: Change the above read to use read transaction ds.resetStatus(); ds.writeBytes(ba.constData(), ba.length()); // Flush the socket because we might not return to the event loop! if (!socket->waitForBytesWritten(2000)) { qWarning() << "Failed to write response:" << socket->error(); } // Wait for the ack bool r = true; r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); if (!socket->waitForDisconnected(2000)) { qWarning() << "Remote not disconnected:" << socket->error(); // Wait again socket->disconnectFromServer(); if (socket->waitForDisconnected(2000)) { qWarning() << "Disconnect timed out:" << socket->error(); } } if (!messageBoxWarningText.isEmpty()) { // Defer the message box to the event loop QTimer::singleShot(0, [messageBoxWarningText]() { QMessageBox::warning(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita"), messageBoxWarningText); }); } } void QMic::pluginStateChanged(QProcess::ProcessState state) { - qDebug() << "stateChanged" << state; + dbgPlugins << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { - qDebug() << "pluginFinished" << exitCode << exitStatus; + dbgPlugins << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { - qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg; + dbgPlugins << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { - qDebug() << "slotStartApplicator();" << gmicImages; + dbgPlugins << "slotStartApplicator();" << gmicImages; if (!viewManager()) return; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 4); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); - qDebug() << key << layerName << width << height; + dbgPlugins << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { - qDebug() << "Could not lock memeory segment" << m.error() << m.errorString(); + dbgPlugins << "Could not lock memeory segment" << m.error() << m.errorString(); } - qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data(); + dbgPlugins << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; - qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); + dbgPlugins << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); - qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height; + dbgPlugins << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { - qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString(); + dbgPlugins << "Could not unlock memeory segment" << m.error() << m.errorString(); } if (!m.detach()) { - qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString(); + dbgPlugins << "Could not detach from memeory segment" << m.error() << m.errorString(); } images.append(gimg); } } - qDebug() << "Got" << images.size() << "gmic images"; + dbgPlugins << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = viewManager()->image()->root(); KisInputOutputMapper mapper(viewManager()->image(), viewManager()->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(viewManager()->image(), rootNode, images, actionName, layers); - m_gmicApplicator->preview(); + m_gmicApplicator->apply(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { if (!viewManager()) return false; viewManager()->image()->lock(); m_inputMode = (InputLayerMode)inputMode; - qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; + dbgPlugins << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(viewManager()->image(), viewManager()->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { viewManager()->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node && node->paintDevice()) { QRect cropRect; KisSelectionSP selection = viewManager()->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = viewManager()->image()->bounds(); } - qDebug() << "Converting node" << node->name() << cropRect; + dbgPlugins << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } - qDebug() << QString::fromUtf8(*message); + dbgPlugins << QString::fromUtf8(*message); viewManager()->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp b/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp index ece1ce00af..ad1884c4c7 100644 --- a/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp +++ b/plugins/extensions/qmic/kis_import_qmic_processing_visitor.cpp @@ -1,95 +1,95 @@ /* * Copyright (c) 2013 Dmitry Kazakov * Copyright (c) 2013 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "kis_qmic_simple_convertor.h" #include #include #include #include #include #include #include #include "kis_import_qmic_processing_visitor.h" #include "gmic.h" KisImportQmicProcessingVisitor::KisImportQmicProcessingVisitor(const KisNodeListSP nodes, QVector *> images, const QRect &dstRect, KisSelectionSP selection) : m_nodes(nodes), m_images(images), m_dstRect(dstRect), m_selection(selection) { - qDebug() << "KisImportQmicProcessingVisitor"; + dbgPlugins << "KisImportQmicProcessingVisitor"; } void KisImportQmicProcessingVisitor::gmicImageToPaintDevice(gmic_image& srcGmicImage, KisPaintDeviceSP dst, KisSelectionSP selection, const QRect &dstRect) { - qDebug() << "KisImportQmicProcessingVisitor::gmicImageToPaintDevice();"; + dbgPlugins << "KisImportQmicProcessingVisitor::gmicImageToPaintDevice();"; if (selection) { KisPaintDeviceSP src = new KisPaintDevice(dst->colorSpace()); KisQmicSimpleConvertor::convertFromGmicFast(srcGmicImage, src, 255.0f); KisPainter painter(dst, selection); painter.setCompositeOp(COMPOSITE_COPY); painter.bitBlt(dstRect.topLeft(), src, QRect(QPoint(0,0),dstRect.size())); } else { KisQmicSimpleConvertor::convertFromGmicFast(srcGmicImage, dst, 255.0f); } } void KisImportQmicProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) { int index = m_nodes->indexOf(node); if (index >= 0) { gmic_image *gimg = m_images[index]; - qDebug() << "Importing layer index" << index << "Size: "<< gimg->_width << "x" << gimg->_height << "colorchannels: " << gimg->_spectrum; + dbgPlugins << "Importing layer index" << index << "Size: "<< gimg->_width << "x" << gimg->_height << "colorchannels: " << gimg->_spectrum; KisPaintDeviceSP dst = node->paintDevice(); KisTransaction transaction(dst); KisImportQmicProcessingVisitor::gmicImageToPaintDevice(*gimg, dst, m_selection, m_dstRect); if (undoAdapter) { transaction.commit(undoAdapter); node->setDirty(m_dstRect); } } } void KisImportQmicProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { Q_UNUSED(layer); Q_UNUSED(undoAdapter); } void KisImportQmicProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) { Q_UNUSED(mask); Q_UNUSED(undoAdapter); } diff --git a/plugins/extensions/qmic/kis_qmic_applicator.cpp b/plugins/extensions/qmic/kis_qmic_applicator.cpp index ca00c91b79..6109453acb 100644 --- a/plugins/extensions/qmic/kis_qmic_applicator.cpp +++ b/plugins/extensions/qmic/kis_qmic_applicator.cpp @@ -1,138 +1,132 @@ /* * Copyright (c) 2013 Lukáš Tvrdý #include #include #include #include "kis_import_qmic_processing_visitor.h" #include "kis_qmic_synchronize_layers_command.h" #include "kis_qmic_synchronize_image_size_command.h" KisQmicApplicator::KisQmicApplicator():m_applicator(0),m_applicatorStrokeEnded(false) { } KisQmicApplicator::~KisQmicApplicator() { delete m_applicator; } void KisQmicApplicator::setProperties(KisImageWSP image, KisNodeSP node, QVector *> images, const KUndo2MagicString &actionName, KisNodeListSP kritaNodes) { - qDebug() << "KisQmicApplicator::setProperties();" << ppVar(image) << ppVar(node) << images.size() << actionName << kritaNodes->count(); + dbgPlugins << "KisQmicApplicator::setProperties();" << ppVar(image) << ppVar(node) << images.size() << actionName << kritaNodes->count(); m_image = image; m_node = node; m_actionName = actionName; m_kritaNodes = kritaNodes; m_images = images; } -void KisQmicApplicator::preview() +void KisQmicApplicator::apply() { - // cancel previous preview if there is one - qDebug() << "Request for preview, cancelling any previous possible on-canvas preview"; + dbgPlugins << "Request for applying the result"; cancel(); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal() << ModifiedSignal; m_applicator = new KisProcessingApplicator(m_image, m_node, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, m_actionName); - qDebug() << "Created applicator " << m_applicator; + dbgPlugins << "Created applicator " << m_applicator; m_gmicData = KisQmicDataSP(new KisQmicData()); QRect layerSize; KisSelectionSP selection = m_image->globalSelection(); if (selection) { layerSize = selection->selectedExactRect(); } else { layerSize = QRect(0, 0, m_image->width(), m_image->height()); } if (!selection) { // synchronize Krita image size with biggest gmic layer size m_applicator->applyCommand(new KisQmicSynchronizeImageSizeCommand(m_images, m_image)); } // synchronize layer count m_applicator->applyCommand(new KisQmicSynchronizeLayersCommand(m_kritaNodes, m_images, m_image, layerSize, selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); KisProcessingVisitorSP importVisitor = new KisImportQmicProcessingVisitor(m_kritaNodes, m_images, layerSize, selection); m_applicator->applyVisitor(importVisitor, KisStrokeJobData::SEQUENTIAL); // undo information is stored in this visitor m_applicator->explicitlyEmitFinalSignals(); emit gmicFinished(true, 0, "done!"); } void KisQmicApplicator::cancel() { - qDebug() << "KisQmicApplicator::cancel"; + dbgPlugins << "KisQmicApplicator::cancel"; if (m_applicator) { - if (!m_applicatorStrokeEnded) - { + if (!m_applicatorStrokeEnded) { dbgPlugins << "Cancelling applicator: Yes!"; m_applicator->cancel(); } - else - { + else { dbgPlugins << "Cancelling applicator: No! Reason: Already finished!"; } - dbgPlugins << "deleting applicator: " << m_applicator; delete m_applicator; m_applicator = 0; - m_applicatorStrokeEnded = false; dbgPlugins << ppVar(m_applicatorStrokeEnded); } else { dbgPlugins << "Cancelling applicator: No! Reason: Null applicator!"; } } void KisQmicApplicator::finish() { - qDebug() << "Applicator " << m_applicator << " finished"; - if (m_applicator) - { + dbgPlugins << "Applicator " << m_applicator << " finished"; + if (m_applicator) { m_applicator->end(); m_applicatorStrokeEnded = true; } - qDebug() << ppVar(m_applicatorStrokeEnded); + dbgPlugins << ppVar(m_applicatorStrokeEnded); } float KisQmicApplicator::getProgress() const { - qDebug() << "KisQmicApplicator::getProgress"; + dbgPlugins << "KisQmicApplicator::getProgress"; if (m_gmicData) { m_gmicData->progress(); } return KisQmicData::INVALID_PROGRESS_VALUE; } diff --git a/plugins/extensions/qmic/kis_qmic_applicator.h b/plugins/extensions/qmic/kis_qmic_applicator.h index 84f66b4568..46c164cf32 100644 --- a/plugins/extensions/qmic/kis_qmic_applicator.h +++ b/plugins/extensions/qmic/kis_qmic_applicator.h @@ -1,64 +1,64 @@ /* * Copyright (c) 2013-2014 Lukáš Tvrdý #include #include #include "gmic.h" #include "kis_qmic_data.h" class KisProcessingApplicator; class KisQmicApplicator : public QObject { Q_OBJECT public: KisQmicApplicator(); ~KisQmicApplicator(); void setProperties(KisImageWSP image, KisNodeSP node, QVector *> images, const KUndo2MagicString &actionName, KisNodeListSP kritaNodes); - void preview(); + void apply(); void cancel(); void finish(); float getProgress() const; Q_SIGNALS: void gmicFinished(bool successfully, int milliseconds = -1, const QString &msg = QString()); private: KisProcessingApplicator *m_applicator; KisImageWSP m_image; KisNodeSP m_node; KUndo2MagicString m_actionName; KisNodeListSP m_kritaNodes; bool m_applicatorStrokeEnded; QVector *> m_images; KisQmicDataSP m_gmicData; }; #endif diff --git a/plugins/extensions/qmic/kis_qmic_data.cpp b/plugins/extensions/qmic/kis_qmic_data.cpp index 0cfa2ced11..c722df1887 100644 --- a/plugins/extensions/qmic/kis_qmic_data.cpp +++ b/plugins/extensions/qmic/kis_qmic_data.cpp @@ -1,37 +1,38 @@ /* * Copyright (c) 2015 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_qmic_data.h" +#include const float KisQmicData::INVALID_PROGRESS_VALUE = -2.0f; KisQmicData::KisQmicData() : m_progress(INVALID_PROGRESS_VALUE) , m_cancel(false) { } KisQmicData::~KisQmicData() { } void KisQmicData::reset() { m_progress = INVALID_PROGRESS_VALUE; m_cancel = false; } diff --git a/plugins/extensions/qmic/kis_qmic_data.h b/plugins/extensions/qmic/kis_qmic_data.h index d80960f871..7ce7029362 100644 --- a/plugins/extensions/qmic/kis_qmic_data.h +++ b/plugins/extensions/qmic/kis_qmic_data.h @@ -1,49 +1,49 @@ /* * Copyright (c) 2015 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_QMIC_DATA #define KIS_QMIC_DATA #include class KisQmicData { public: KisQmicData(); ~KisQmicData(); - float progress() const { qDebug() << "KisQmicData::progress" << m_progress; return m_progress; } + float progress() const { return m_progress; } bool isCancelled() const { return m_cancel; } void setCancel(bool cancel) { m_cancel = cancel; } bool & cancelPtr() { return m_cancel; } float & progressPtr() { return m_progress; } void reset(); static const float INVALID_PROGRESS_VALUE; private: float m_progress; bool m_cancel; }; #include typedef QSharedPointer KisQmicDataSP; #endif diff --git a/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp b/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp index fb9ea32d7c..5c76518fe5 100644 --- a/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp +++ b/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp @@ -1,880 +1,873 @@ /* * Copyright (c) 2013 Lukáš Tvrdý #include #include #include #include #include #include #define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v ) #define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v ) template class KisColorToFloatConvertor : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorToFloatConvertor(float gmicUnitValue = 255.0f) : m_gmicUnitValue(gmicUnitValue) {} float m_gmicUnitValue; void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { float gmicUnitValue2KritaUnitValue = m_gmicUnitValue / KoColorSpaceMathsTraits::unitValue; const RGBPixel* srcPixel = reinterpret_cast(src); KoRgbF32Traits::Pixel* dstPixel = reinterpret_cast(dst); while(nPixels > 0) { dstPixel->red = SCALE_TO_FLOAT(srcPixel->red) * gmicUnitValue2KritaUnitValue; dstPixel->green = SCALE_TO_FLOAT(srcPixel->green) * gmicUnitValue2KritaUnitValue; dstPixel->blue = SCALE_TO_FLOAT(srcPixel->blue) * gmicUnitValue2KritaUnitValue; dstPixel->alpha = SCALE_TO_FLOAT(srcPixel->alpha) * gmicUnitValue2KritaUnitValue; --nPixels; ++srcPixel; ++dstPixel; } } }; template class KisColorFromFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromFloat(float gmicUnitValue = 255.0f) : m_gmicUnitValue(gmicUnitValue) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; while(nPixels > 0) { dstPixel->red = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->green = SCALE_FROM_FLOAT(srcPixel->green * gmicUnitValue2KritaUnitValue); dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->blue * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->alpha * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; template class KisColorFromGrayScaleFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromGrayScaleFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; // warning: green and blue channels on input contain random data!!! see that we copy only one channel // when gmic image has grayscale colorspace while(nPixels > 0) { dstPixel->red = dstPixel->green = dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->alpha * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; template class KisColorFromGrayScaleAlphaFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromGrayScaleAlphaFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; // warning: green and blue channels on input contain random data!!! see that we copy only one channel // when gmic image has grayscale colorspace while(nPixels > 0) { dstPixel->red = dstPixel->green = dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->green * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; static KoColorTransformation* createTransformationFromGmic(const KoColorSpace* colorSpace, quint32 gmicSpectrum,float gmicUnitValue) { KoColorTransformation * colorTransformation = 0; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space for fast pixel transformation to gmic pixel format" << colorSpace->id(); return 0; } if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< float, KoRgbTraits < float > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat >(gmicUnitValue); } } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< half, KoRgbTraits < half > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< half, KoRgbTraits < half > >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< half, KoRgbTraits < half > >(gmicUnitValue); } } #endif else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } } else if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel transformation to gmic pixel format"; return 0; } return colorTransformation; } static KoColorTransformation* createTransformation(const KoColorSpace* colorSpace) { KoColorTransformation * colorTransformation = 0; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space for fast pixel transformation to gmic pixel format" << colorSpace->id(); return 0; } if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< float, KoRgbTraits < float > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< quint16, KoBgrTraits < quint16 > >(); } else if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< quint8, KoBgrTraits < quint8 > >(); } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel transformation to gmic pixel format"; return 0; } return colorTransformation; } void KisQmicSimpleConvertor::convertFromGmicFast(gmic_image& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue) { + dbgPlugins << "convertFromGmicFast"; const KoColorSpace * dstColorSpace = dst->colorSpace(); - KoColorTransformation * gmicToDstPixelFormat = createTransformationFromGmic(dstColorSpace,gmicImage._spectrum,gmicUnitValue); - if (gmicToDstPixelFormat == 0) - { + KoColorTransformation * gmicToDstPixelFormat = createTransformationFromGmic(dstColorSpace, gmicImage._spectrum, gmicUnitValue); + if (gmicToDstPixelFormat == 0) { dbgPlugins << "Fall-back to slow color conversion"; convertFromGmicImage(gmicImage, dst, gmicUnitValue); return; } qint32 x = 0; qint32 y = 0; qint32 width = gmicImage._width; qint32 height = gmicImage._height; width = width < 0 ? 0 : width; height = height < 0 ? 0 : height; - const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); // this function always convert to rgba or rgb with various color depth quint32 dstNumChannels = rgbaFloat32bitcolorSpace->channelCount(); + // number of channels that we will copy quint32 numChannels = gmicImage._spectrum; // gmic image has 4, 3, 2, 1 channel QVector planes(dstNumChannels); int channelOffset = gmicImage._width * gmicImage._height; - for (unsigned int channelIndex = 0; channelIndex < gmicImage._spectrum; channelIndex++) - { + for (unsigned int channelIndex = 0; channelIndex < gmicImage._spectrum; channelIndex++) { planes[channelIndex] = gmicImage._data + channelOffset * channelIndex; } - for (unsigned int channelIndex = gmicImage._spectrum; channelIndex < dstNumChannels; channelIndex++) - { + for (unsigned int channelIndex = gmicImage._spectrum; channelIndex < dstNumChannels; channelIndex++) { planes[channelIndex] = 0; //turn off } qint32 dataY = 0; qint32 imageY = y; qint32 rowsRemaining = height; const qint32 floatPixelSize = rgbaFloat32bitcolorSpace->pixelSize(); KisRandomAccessorSP it = dst->createRandomAccessorNG(dst->x(), dst->y()); // 0,0 int tileWidth = it->numContiguousColumns(dst->x()); int tileHeight = it->numContiguousRows(dst->y()); Q_ASSERT(tileWidth == 64); Q_ASSERT(tileHeight == 64); - quint8 * convertedTile = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * tileWidth * tileHeight]; + quint8 *convertedTile = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * tileWidth * tileHeight]; // grayscale and rgb case does not have alpha, so let's fill 4th channel of rgba tile with opacity opaque - if (gmicImage._spectrum == 1 || gmicImage._spectrum == 3) - { + if (gmicImage._spectrum == 1 || gmicImage._spectrum == 3) { quint32 nPixels = tileWidth * tileHeight; quint32 pixelIndex = 0; KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(convertedTile); - while (pixelIndex < nPixels) - { + while (pixelIndex < nPixels) { srcPixel->alpha = gmicUnitValue; ++srcPixel; ++pixelIndex; } } while (rowsRemaining > 0) { qint32 dataX = 0; qint32 imageX = x; qint32 columnsRemaining = width; qint32 numContiguousImageRows = it->numContiguousRows(imageY); qint32 rowsToWork = qMin(numContiguousImageRows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousImageColumns = it->numContiguousColumns(imageX); qint32 columnsToWork = qMin(numContiguousImageColumns, columnsRemaining); const qint32 dataIdx = dataX + dataY * width; const qint32 tileRowStride = (tileWidth - columnsToWork) * floatPixelSize; quint8 *tileItStart = convertedTile; // copy gmic channels to float tile qint32 channelSize = sizeof(float); for(quint32 i=0; imoveTo(imageX, imageY); quint8 *dstTileItStart = it->rawData(); tileItStart = convertedTile; // back to the start of the converted tile // copy float tile to dst colorspace based on input colorspace (rgb or grayscale) for (qint32 row = 0; row < rowsToWork; row++) { gmicToDstPixelFormat->transform(tileItStart, dstTileItStart, columnsToWork); dstTileItStart += dstColorSpace->pixelSize() * tileWidth; tileItStart += floatPixelSize * tileWidth; } imageX += columnsToWork; dataX += columnsToWork; columnsRemaining -= columnsToWork; } imageY += rowsToWork; dataY += rowsToWork; rowsRemaining -= rowsToWork; } delete [] convertedTile; delete gmicToDstPixelFormat; } void KisQmicSimpleConvertor::convertToGmicImageFast(KisPaintDeviceSP dev, gmic_image *gmicImage, QRect rc) { KoColorTransformation * pixelToGmicPixelFormat = createTransformation(dev->colorSpace()); if (pixelToGmicPixelFormat == 0) { - qDebug() << "Fall-back to slow color conversion method"; + dbgPlugins << "Fall-back to slow color conversion method"; convertToGmicImage(dev, gmicImage, rc); return; } if (rc.isEmpty()) { - qDebug() << "Image rectangle is empty! Using supplied gmic layer dimension"; + dbgPlugins << "Image rectangle is empty! Using supplied gmic layer dimension"; rc = QRect(0, 0, gmicImage->_width, gmicImage->_height); } qint32 x = rc.x(); qint32 y = rc.y(); qint32 width = rc.width(); qint32 height = rc.height(); width = width < 0 ? 0 : width; height = height < 0 ? 0 : height; const qint32 numChannels = 4; int greenOffset = gmicImage->_width * gmicImage->_height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; QVector planes; planes.append(gmicImage->_data); planes.append(gmicImage->_data + greenOffset); planes.append(gmicImage->_data + blueOffset); planes.append(gmicImage->_data + alphaOffset); KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(dev->x(), dev->y()); int tileWidth = it->numContiguousColumns(dev->x()); int tileHeight = it->numContiguousRows(dev->y()); Q_ASSERT(tileWidth == 64); Q_ASSERT(tileHeight == 64); const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); Q_CHECK_PTR(rgbaFloat32bitcolorSpace); const qint32 dstPixelSize = rgbaFloat32bitcolorSpace->pixelSize(); const qint32 srcPixelSize = dev->pixelSize(); quint8 * dstTile = new quint8[dstPixelSize * tileWidth * tileHeight]; qint32 dataY = 0; qint32 imageX = x; qint32 imageY = y; it->moveTo(imageX, imageY); qint32 rowsRemaining = height; while (rowsRemaining > 0) { qint32 dataX = 0; imageX = x; qint32 columnsRemaining = width; qint32 numContiguousImageRows = it->numContiguousRows(imageY); qint32 rowsToWork = qMin(numContiguousImageRows, rowsRemaining); qint32 convertedTileY = tileHeight - rowsToWork; Q_ASSERT(convertedTileY >= 0); while (columnsRemaining > 0) { qint32 numContiguousImageColumns = it->numContiguousColumns(imageX); qint32 columnsToWork = qMin(numContiguousImageColumns, columnsRemaining); qint32 convertedTileX = tileWidth - columnsToWork; Q_ASSERT(convertedTileX >= 0); const qint32 dataIdx = dataX + dataY * width; const qint32 dstTileIndex = convertedTileX + convertedTileY * tileWidth; const qint32 tileRowStride = (tileWidth - columnsToWork) * dstPixelSize; const qint32 srcTileRowStride = (tileWidth - columnsToWork) * srcPixelSize; it->moveTo(imageX, imageY); quint8 *tileItStart = dstTile + dstTileIndex * dstPixelSize; // transform tile row by row quint8 *dstTileIt = tileItStart; quint8 *srcTileIt = const_cast(it->rawDataConst()); qint32 row = rowsToWork; while (row > 0) { pixelToGmicPixelFormat->transform(srcTileIt, dstTileIt , columnsToWork); srcTileIt += columnsToWork * srcPixelSize; srcTileIt += srcTileRowStride; dstTileIt += columnsToWork * dstPixelSize; dstTileIt += tileRowStride; row--; } // here we want to copy floats to dstTile, so tileItStart has to point to float buffer qint32 channelSize = sizeof(float); for(qint32 i = 0; i< numChannels; i++) { float * planeIt = planes[i] + dataIdx; qint32 dataStride = (width - columnsToWork); quint8* tileIt = tileItStart; for (qint32 row = 0; row < rowsToWork; row++) { for (int col = 0; col < columnsToWork; col++) { memcpy(planeIt, tileIt, channelSize); tileIt += dstPixelSize; planeIt += 1; } tileIt += tileRowStride; planeIt += dataStride; } // skip channel in tile: red, green, blue, alpha tileItStart += channelSize; } imageX += columnsToWork; dataX += columnsToWork; columnsRemaining -= columnsToWork; } imageY += rowsToWork; dataY += rowsToWork; rowsRemaining -= rowsToWork; } delete [] dstTile; delete pixelToGmicPixelFormat; } // gmic assumes float rgba in 0.0 - 255.0 void KisQmicSimpleConvertor::convertToGmicImage(KisPaintDeviceSP dev, gmic_image *gmicImage, QRect rc) { Q_ASSERT(!dev.isNull()); Q_ASSERT(gmicImage->_spectrum == 4); // rgba if (rc.isEmpty()) { rc = QRect(0, 0, gmicImage->_width, gmicImage->_height); } const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); Q_CHECK_PTR(rgbaFloat32bitcolorSpace); KoColorTransformation *pixelToGmicPixelFormat = createTransformation(rgbaFloat32bitcolorSpace); int greenOffset = gmicImage->_width * gmicImage->_height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); const KoColorSpace * colorSpace = dev->colorSpace(); KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(0,0); int optimalBufferSize = 64; // most common numContiguousColumns, tile size? quint8 * floatRGBApixel = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * optimalBufferSize]; quint32 pixelSize = rgbaFloat32bitcolorSpace->pixelSize(); int pos = 0; for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(rc.x() + x, rc.y() + y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(rc.x() + x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); colorSpace->convertPixelsTo(it->rawDataConst(), floatRGBApixel, rgbaFloat32bitcolorSpace, numContiguousColumns, renderingIntent, conversionFlags); pixelToGmicPixelFormat->transform(floatRGBApixel, floatRGBApixel, numContiguousColumns); pos = y * gmicImage->_width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { memcpy(gmicImage->_data + pos ,floatRGBApixel + bx * pixelSize , 4); memcpy(gmicImage->_data + pos + greenOffset ,floatRGBApixel + bx * pixelSize + 4, 4); memcpy(gmicImage->_data + pos + blueOffset ,floatRGBApixel + bx * pixelSize + 8, 4); memcpy(gmicImage->_data + pos + alphaOffset ,floatRGBApixel + bx * pixelSize + 12, 4); pos++; } x += numContiguousColumns; } } delete [] floatRGBApixel; delete pixelToGmicPixelFormat; } void KisQmicSimpleConvertor::convertFromGmicImage(gmic_image& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue) { + dbgPlugins << "convertFromGmicSlow"; Q_ASSERT(!dst.isNull()); const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); const KoColorSpace *dstColorSpace = dst->colorSpace(); - if (dstColorSpace == 0) - { - dstColorSpace = rgbaFloat32bitcolorSpace; - } KisPaintDeviceSP dev = dst; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; QRect rc(0,0,gmicImage._width, gmicImage._height); KisRandomAccessorSP it = dev->createRandomAccessorNG(0,0); int pos; float r,g,b,a; int optimalBufferSize = 64; // most common numContiguousColumns, tile size? quint8 * floatRGBApixel = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * optimalBufferSize]; quint32 pixelSize = rgbaFloat32bitcolorSpace->pixelSize(); KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); // Krita needs rgba in 0.0...1.0 float multiplied = KoColorSpaceMathsTraits::unitValue / gmicMaxChannelValue; switch (gmicImage._spectrum) { case 1: { // convert grayscale to rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = g = b = gmicImage._data[pos] * multiplied; a = KoColorSpaceMathsTraits::unitValue; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 2: { // convert grayscale alpha to rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = g = b = gmicImage._data[pos] * multiplied; a = gmicImage._data[pos + greenOffset] * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 3: { // convert rgb -> rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = gmicImage._data[pos] * multiplied; g = gmicImage._data[pos + greenOffset] * multiplied; b = gmicImage._data[pos + blueOffset ] * multiplied; a = gmicMaxChannelValue * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 4: { for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = gmicImage._data[pos] * multiplied; g = gmicImage._data[pos + greenOffset] * multiplied; b = gmicImage._data[pos + blueOffset ] * multiplied; a = gmicImage._data[pos + alphaOffset] * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } default: { dbgPlugins << "Unsupported gmic output format : " << gmicImage._width << gmicImage._height << gmicImage._depth << gmicImage._spectrum; } } } QImage KisQmicSimpleConvertor::convertToQImage(gmic_image& gmicImage, float gmicActualMaxChannelValue) { QImage image = QImage(gmicImage._width, gmicImage._height, QImage::Format_ARGB32); dbgPlugins << image.format() <<"first pixel:"<< gmicImage._data[0] << gmicImage._width << gmicImage._height << gmicImage._spectrum; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int pos = 0; // always put 255 to qimage float multiplied = 255.0f / gmicActualMaxChannelValue; for (unsigned int y = 0; y < gmicImage._height; y++) { QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (unsigned int x = 0; x < gmicImage._width; x++) { pos = y * gmicImage._width + x; float r = gmicImage._data[pos] * multiplied; float g = gmicImage._data[pos + greenOffset] * multiplied; float b = gmicImage._data[pos + blueOffset] * multiplied; pixel[x] = qRgb(int(r),int(g), int(b)); } } return image; } void KisQmicSimpleConvertor::convertFromQImage(const QImage &image, gmic_image *gmicImage, float gmicUnitValue) { int greenOffset = gmicImage->_width * gmicImage->_height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; int pos = 0; // QImage has 0..255 float qimageUnitValue = 255.0f; float multiplied = gmicUnitValue / qimageUnitValue; Q_ASSERT(image.width() == int(gmicImage->_width)); Q_ASSERT(image.height() == int(gmicImage->_height)); Q_ASSERT(image.format() == QImage::Format_ARGB32); switch (gmicImage->_spectrum) { case 1: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qGray(pixel[x]) * multiplied; } } break; } case 2: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qGray(pixel[x]) * multiplied; gmicImage->_data[pos + greenOffset] = qAlpha(pixel[x]) * multiplied; } } break; } case 3: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qRed(pixel[x]) * multiplied; gmicImage->_data[pos + greenOffset] = qGreen(pixel[x]) * multiplied; gmicImage->_data[pos + blueOffset] = qBlue(pixel[x]) * multiplied; } } break; } case 4: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qRed(pixel[x]) * multiplied; gmicImage->_data[pos + greenOffset] = qGreen(pixel[x]) * multiplied; gmicImage->_data[pos + blueOffset] = qBlue(pixel[x]) * multiplied; gmicImage->_data[pos + alphaOffset] = qAlpha(pixel[x]) * multiplied; } } break; } default: { Q_ASSERT(false); dbgKrita << "Unexpected gmic image format"; break; } } } diff --git a/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp b/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp index bbb583747c..4d739f0e84 100644 --- a/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp +++ b/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp @@ -1,82 +1,82 @@ /* * Copyright (c) 2015 Lukáš Tvrdý KisQmicSynchronizeImageSizeCommand::KisQmicSynchronizeImageSizeCommand(QVector *> images, KisImageWSP image) : m_images(images) , m_image(image) , m_resizeCommand(0) { - qDebug() << "KisQmicSynchronizeImageSizeCommand" << "gmic images" << m_images.size(); + dbgPlugins << "KisQmicSynchronizeImageSizeCommand" << "gmic images" << m_images.size(); } KisQmicSynchronizeImageSizeCommand::~KisQmicSynchronizeImageSizeCommand() { delete m_resizeCommand; } void KisQmicSynchronizeImageSizeCommand::redo() { - qDebug() << "KisQmicSynchronizeImageSizeCommand::redo"; + dbgPlugins << "KisQmicSynchronizeImageSizeCommand::redo"; // sync image size if (m_image) { QSize gmicBoundingLayerSize = findMaxLayerSize(m_images); QSize kritaSize = m_image->size(); - qDebug() << "\tkrita image" << kritaSize << "gmic size" << gmicBoundingLayerSize; + dbgPlugins << "\tkrita image" << kritaSize << "gmic size" << gmicBoundingLayerSize; if (gmicBoundingLayerSize.width() > kritaSize.width() || gmicBoundingLayerSize.height() > kritaSize.height()) { QSize newSize = kritaSize.expandedTo(gmicBoundingLayerSize); dbgPlugins << "G'Mic expands Krita canvas from " << kritaSize << " to " << newSize; m_resizeCommand = new KisImageResizeCommand(m_image, newSize); m_resizeCommand->redo(); } } } void KisQmicSynchronizeImageSizeCommand::undo() { - qDebug() << "KisQmicSynchronizeImageSizeCommand::undo"; + dbgPlugins << "KisQmicSynchronizeImageSizeCommand::undo"; if (m_resizeCommand) { m_resizeCommand->undo(); } } QSize KisQmicSynchronizeImageSizeCommand::findMaxLayerSize(QVector *> images) { // synchronize image size int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < images.size(); i++) { gmic_image *gimg = images[i]; int width = gimg->_width; maxWidth = qMax(width, maxWidth); int height = gimg->_height; maxHeight = qMax(height, maxHeight); } - qDebug() << "MaxLayerSize" << maxWidth << maxHeight; + dbgPlugins << "MaxLayerSize" << maxWidth << maxHeight; return QSize(maxWidth, maxHeight); } diff --git a/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp b/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp index 8ea09eec15..6e2323fba1 100644 --- a/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp +++ b/plugins/extensions/qmic/kis_qmic_synchronize_layers_command.cpp @@ -1,104 +1,103 @@ /* * Copyright (c) 2013 Lukáš Tvrdý #include "kis_import_qmic_processing_visitor.h" #include #include #include #include #include #include KisQmicSynchronizeLayersCommand::KisQmicSynchronizeLayersCommand(KisNodeListSP nodes, QVector *> images, KisImageWSP image, const QRect &dstRect, KisSelectionSP selection) : KUndo2Command(), m_nodes(nodes), m_images(images), m_image(image), m_dstRect(dstRect), m_selection(selection), m_firstRedo(true) { - qDebug() << "KisQmicSynchronizeLayersCommand"; + dbgPlugins << "KisQmicSynchronizeLayersCommand"; } KisQmicSynchronizeLayersCommand::~KisQmicSynchronizeLayersCommand() { qDeleteAll(m_imageCommands); m_imageCommands.clear(); } void KisQmicSynchronizeLayersCommand::redo() { - qDebug() << "KisQmicSynchronizeLayersCommand::Redo" << m_firstRedo; + dbgPlugins << "KisQmicSynchronizeLayersCommand::Redo" << m_firstRedo; if (m_firstRedo) { // if gmic produces more layers if (m_nodes->size() < m_images.size()) { if (m_image) { int nodesCount = m_nodes->size(); for (int i = nodesCount; i < m_images.size(); i++) { KisPaintDevice * device = new KisPaintDevice(m_image->colorSpace()); KisLayerSP paintLayer = new KisPaintLayer(m_image, "New layer from gmic filter", OPACITY_OPAQUE_U8, device); KisImportQmicProcessingVisitor::gmicImageToPaintDevice(*m_images[i], device); KisNodeSP aboveThis = m_nodes->last(); KisNodeSP parent = m_nodes->at(0)->parent(); dbgPlugins << "Adding paint layer " << (i - nodesCount + 1) << " to parent " << parent->name(); KisImageLayerAddCommand *addLayerCmd = new KisImageLayerAddCommand(m_image, paintLayer, parent, aboveThis, false, true); addLayerCmd->redo(); m_imageCommands.append(addLayerCmd); m_nodes->append(paintLayer); } } else // small preview { Q_ASSERT(m_nodes->size() > 0); - for (int i = m_nodes->size(); i < m_images.size(); i++) - { + for (int i = m_nodes->size(); i < m_images.size(); i++) { KisPaintDevice * device = new KisPaintDevice(m_nodes->at(0)->colorSpace()); KisLayerSP paintLayer = new KisPaintLayer(0, "New layer from gmic filter", OPACITY_OPAQUE_U8, device); m_nodes->append(paintLayer); } } } // if gmic produces less layers, we are going to drop some else if (m_nodes->size() > int(m_images.size())) { dbgPlugins << "no support for removing layers yet!!"; } } else { dbgPlugins << "Redo again needed?"; } } void KisQmicSynchronizeLayersCommand::undo() { KisImageCommand * cmd; Q_FOREACH (cmd, m_imageCommands) { cmd->undo(); } } diff --git a/plugins/extensions/qmic/tests/kis_qmic_tests.cpp b/plugins/extensions/qmic/tests/kis_qmic_tests.cpp index 9d83f69db1..8eaedd4606 100644 --- a/plugins/extensions/qmic/tests/kis_qmic_tests.cpp +++ b/plugins/extensions/qmic/tests/kis_qmic_tests.cpp @@ -1,213 +1,203 @@ /* * Copyright (c) 2013 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_qmic_tests.h" #include "../gmic.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif const static QString EXTENSION = ".png"; void KisQmicTests::initTestCase() { m_qimage = QImage(QString(FILES_DATA_DIR) + "/" + "poster_rodents_bunnysize.jpg"); m_qimage = m_qimage.convertToFormat(QImage::Format_ARGB32); - - qDebug() << m_qimage.size(); - - m_qmicImage.assign(m_qimage.width(), m_qimage.height(), 1, 4); // rgba - m_qmicImage._data = new float[m_qimage.width() * m_qimage.height() * 4]; - KisQmicSimpleConvertor::convertFromQImage(m_qimage, &m_qmicImage); } void KisQmicTests::cleanupTestCase() { } void KisQmicTests::testConvertGrayScaleQmic() { KisPaintDeviceSP resultDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP resultDevFast = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); gmic_image qmicImage; - qmicImage.assign(m_qimage.width(),m_qimage.height(), 1, 1); + qmicImage.assign(m_qimage.width(), m_qimage.height(), 1, 1); qmicImage._data = new float[m_qimage.width() * m_qimage.height()]; KisQmicSimpleConvertor::convertFromQImage(m_qimage, &qmicImage, 1.0); KisQmicSimpleConvertor::convertFromGmicImage(qmicImage, resultDev, 1.0); KisQmicSimpleConvertor::convertFromGmicFast(qmicImage, resultDevFast, 1.0); - QImage slowQImage = resultDev->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); - QImage fastQImage = resultDevFast->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); + QImage slowQImage = resultDev->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); + QImage fastQImage = resultDevFast->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); QPoint errpoint; - if (!TestUtil::compareQImages(errpoint, slowQImage, fastQImage)) - { + if (!TestUtil::compareQImages(errpoint, slowQImage, fastQImage)) { QFAIL(QString("Slow method produces different result then fast to convert qmic grayscale pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); slowQImage.save("grayscale.bmp"); fastQImage.save("grayscale_fast.bmp"); } } void KisQmicTests::testConvertGrayScaleAlphaQmic() { KisPaintDeviceSP resultDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP resultDevFast = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); gmic_image qmicImage; qmicImage.assign(m_qimage.width(),m_qimage.height(), 1, 2); qmicImage._data = new float[m_qimage.width() * m_qimage.height() * 2]; KisQmicSimpleConvertor::convertFromQImage(m_qimage, &qmicImage, 1.0); KisQmicSimpleConvertor::convertFromGmicImage(qmicImage, resultDev, 1.0); KisQmicSimpleConvertor::convertFromGmicFast(qmicImage, resultDevFast, 1.0); - QImage slowQImage = resultDev->convertToQImage(0, 0,0,qmicImage._width, qmicImage._height); - QImage fastQImage = resultDevFast->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); + QImage slowQImage = resultDev->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); + QImage fastQImage = resultDevFast->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); QPoint errpoint; - if (!TestUtil::compareQImages(errpoint,slowQImage,fastQImage)) - { + if (!TestUtil::compareQImages(errpoint, slowQImage, fastQImage)) { QFAIL(QString("Slow method produces different result then fast to convert qmic grayscale pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); slowQImage.save("grayscale.bmp"); fastQImage.save("grayscale_fast.bmp"); } } void KisQmicTests::testConvertRGBqmic() { KisPaintDeviceSP resultDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP resultDevFast = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); gmic_image qmicImage; qmicImage.assign(m_qimage.width(), m_qimage.height(), 1, 3); qmicImage._data = new float[m_qimage.width() * m_qimage.height() * 3]; KisQmicSimpleConvertor::convertFromQImage(m_qimage, &qmicImage, 1.0); KisQmicSimpleConvertor::convertFromGmicImage(qmicImage, resultDev, 1.0); KisQmicSimpleConvertor::convertFromGmicFast(qmicImage, resultDevFast, 1.0); - QImage slowQImage = resultDev->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); + QImage slowQImage = resultDev->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); QPoint errpoint; - if (!TestUtil::compareQImages(errpoint, slowQImage, m_qimage)) - { + + if (!TestUtil::compareQImages(errpoint, slowQImage, m_qimage)) { QFAIL(QString("Slow method failed to convert qmic RGB pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); slowQImage.save("RGB.bmp"); } - QImage fastQImage = resultDevFast->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); - if (!TestUtil::compareQImages(errpoint,fastQImage,m_qimage)) - { + QImage fastQImage = resultDevFast->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); + if (!TestUtil::compareQImages(errpoint,fastQImage,m_qimage)) { QFAIL(QString("Fast method failed to convert qmic RGB pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); fastQImage.save("RGB_fast.bmp"); } } void KisQmicTests::testConvertRGBAqmic() { KisPaintDeviceSP resultDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP resultDevFast = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); gmic_image qmicImage; qmicImage.assign(m_qimage.width(),m_qimage.height(), 1, 4); qmicImage._data = new float[m_qimage.width() * m_qimage.height() * 4]; KisQmicSimpleConvertor::convertFromQImage(m_qimage, &qmicImage, 1.0); KisQmicSimpleConvertor::convertFromGmicImage(qmicImage, resultDev, 1.0); KisQmicSimpleConvertor::convertFromGmicFast(qmicImage, resultDevFast, 1.0); - QImage slowQImage = resultDev->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); + QImage slowQImage = resultDev->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); QPoint errpoint; - if (!TestUtil::compareQImages(errpoint,slowQImage,m_qimage)) - { + if (!TestUtil::compareQImages(errpoint,slowQImage,m_qimage)) { QFAIL(QString("Slow method failed to convert qmic RGBA pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); slowQImage.save("RGBA.bmp"); } - QImage fastQImage = resultDevFast->convertToQImage(0,0,0,qmicImage._width, qmicImage._height); - if (!TestUtil::compareQImages(errpoint,fastQImage,m_qimage)) - { + QImage fastQImage = resultDevFast->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); + if (!TestUtil::compareQImages(errpoint,fastQImage,m_qimage)) { QFAIL(QString("Fast method failed to convert qmic RGBA pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); fastQImage.save("RGBA_fast.bmp"); } } void KisQmicTests::testConvertToGmic() { KisPaintDeviceSP srcDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); srcDev->convertFromQImage(m_qimage, 0); gmic_image qmicImage; qmicImage.assign(m_qimage.width(),m_qimage.height(), 1, 4); qmicImage._data = new float[m_qimage.width() * m_qimage.height() * 4]; KisQmicSimpleConvertor::convertToGmicImageFast(srcDev, &qmicImage, srcDev->exactBounds()); - KisPaintDeviceSP resultDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); - KisPaintDeviceSP resultDevFast = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); - - KisQmicSimpleConvertor::convertFromGmicImage(qmicImage, resultDev, 1.0); - KisQmicSimpleConvertor::convertFromGmicFast(qmicImage, resultDevFast, 1.0); - - QImage slowQImage = resultDev->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); - slowQImage.save("a.png"); QPoint errpoint; - if (!TestUtil::compareQImages(errpoint, slowQImage, m_qimage)) - { - QFAIL(QString("Slow method failed to convert qmic RGBA pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); - slowQImage.save("RGBA.bmp"); + QImage resultQImage = KisQmicSimpleConvertor::convertToQImage(qmicImage); + if (!TestUtil::compareQImages(errpoint, resultQImage, m_qimage)) { + QFAIL(QString("Failed to convert qmic RGBA pixel format to QImage, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + resultQImage.save("testConvertToGmic_qimage.bmp"); } + KisPaintDeviceSP resultDevFast = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + KisQmicSimpleConvertor::convertFromGmicFast(qmicImage, resultDevFast, 255.0f); QImage fastQImage = resultDevFast->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); - fastQImage.save("b.png"); - if (!TestUtil::compareQImages(errpoint, fastQImage, m_qimage)) - { + + if (!TestUtil::compareQImages(errpoint, fastQImage, m_qimage)) { QFAIL(QString("Fast method failed to convert qmic RGBA pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); - fastQImage.save("RGBA_fast.bmp"); + fastQImage.save("testConvertToGmic_fast.bmp"); + } + + KisPaintDeviceSP resultDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + KisQmicSimpleConvertor::convertFromGmicImage(qmicImage, resultDev, 255.0f); + QImage slowQImage = resultDev->convertToQImage(0, 0, 0, qmicImage._width, qmicImage._height); + if (!TestUtil::compareQImages(errpoint, slowQImage, m_qimage)) { + QFAIL(QString("Slow method failed to convert qmic RGBA pixel format, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + slowQImage.save("testConvertToGmic_slow.bmp"); } } QTEST_MAIN(KisQmicTests) diff --git a/plugins/extensions/qmic/tests/kis_qmic_tests.h b/plugins/extensions/qmic/tests/kis_qmic_tests.h index 0badb80e31..433183d76d 100644 --- a/plugins/extensions/qmic/tests/kis_qmic_tests.h +++ b/plugins/extensions/qmic/tests/kis_qmic_tests.h @@ -1,46 +1,45 @@ -/* +/*ls * Copyright (c) 2013 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_QMIC_TESTS_H_ #define _KIS_QMIC_TESTS_H_ #include #include #include "../gmic.h" class KisQmicTests : public QObject { Q_OBJECT private: - gmic_image m_qmicImage; QImage m_qimage; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); - + void testConvertToGmic(); void testConvertGrayScaleQmic(); void testConvertGrayScaleAlphaQmic(); void testConvertRGBqmic(); void testConvertRGBAqmic(); - void testConvertToGmic(); + }; #endif diff --git a/plugins/filters/tests/data/asc-cdl.cfg b/plugins/filters/tests/data/asc-cdl.cfg new file mode 100644 index 0000000000..b6cba394e4 --- /dev/null +++ b/plugins/filters/tests/data/asc-cdl.cfg @@ -0,0 +1,18 @@ + + + + + + +]]> + + + + +]]> + + + + +]]> + diff --git a/plugins/filters/tests/data/blur.cfg b/plugins/filters/tests/data/blur.cfg new file mode 100644 index 0000000000..7b4987a2fb --- /dev/null +++ b/plugins/filters/tests/data/blur.cfg @@ -0,0 +1,8 @@ + + + 5 + 5 + 0 + 0 + 0 + diff --git a/plugins/filters/tests/data/burn.cfg b/plugins/filters/tests/data/burn.cfg new file mode 100644 index 0000000000..962ef855d1 --- /dev/null +++ b/plugins/filters/tests/data/burn.cfg @@ -0,0 +1,2 @@ + + diff --git a/plugins/filters/tests/data/colorbalance.cfg b/plugins/filters/tests/data/colorbalance.cfg new file mode 100644 index 0000000000..44c31070fe --- /dev/null +++ b/plugins/filters/tests/data/colorbalance.cfg @@ -0,0 +1,13 @@ + + + 0 + 0 + 0 + 0 + 0 + 0 + true + 0 + 0 + 0 + diff --git a/plugins/filters/tests/data/crosschannel.cfg b/plugins/filters/tests/data/crosschannel.cfg new file mode 100644 index 0000000000..fcaa8c129c --- /dev/null +++ b/plugins/filters/tests/data/crosschannel.cfg @@ -0,0 +1,4 @@ + + + 0 + diff --git a/plugins/filters/tests/data/dodge.cfg b/plugins/filters/tests/data/dodge.cfg new file mode 100644 index 0000000000..962ef855d1 --- /dev/null +++ b/plugins/filters/tests/data/dodge.cfg @@ -0,0 +1,2 @@ + + diff --git a/plugins/filters/tests/data/edge detection.cfg b/plugins/filters/tests/data/edge detection.cfg new file mode 100644 index 0000000000..899388d417 --- /dev/null +++ b/plugins/filters/tests/data/edge detection.cfg @@ -0,0 +1,9 @@ + + + 1 + true + + false + + 1 + diff --git a/plugins/filters/tests/data/gradientmap.cfg b/plugins/filters/tests/data/gradientmap.cfg new file mode 100644 index 0000000000..2ec96a2ba6 --- /dev/null +++ b/plugins/filters/tests/data/gradientmap.cfg @@ -0,0 +1,5 @@ + + + +]]> + diff --git a/plugins/filters/tests/data/halftone.cfg b/plugins/filters/tests/data/halftone.cfg new file mode 100644 index 0000000000..29ae1ba783 --- /dev/null +++ b/plugins/filters/tests/data/halftone.cfg @@ -0,0 +1,17 @@ + + + true + + + + +]]> + 8 + + + + +]]> + false + 45 + diff --git a/plugins/flake/artistictextshape/ArtisticTextShape.cpp b/plugins/flake/artistictextshape/ArtisticTextShape.cpp index e6ffec0866..b80d7befc1 100644 --- a/plugins/flake/artistictextshape/ArtisticTextShape.cpp +++ b/plugins/flake/artistictextshape/ArtisticTextShape.cpp @@ -1,1402 +1,1402 @@ /* This file is part of the KDE project * Copyright (C) 2007-2009,2011 Jan Hambrecht * Copyright (C) 2008 Rob Buis * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ArtisticTextShape.h" #include "ArtisticTextLoadingContext.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ArtisticTextShape::ArtisticTextShape() : m_path(0) , m_startOffset(0.0) , m_textAnchor(AnchorStart) , m_textUpdateCounter(0) , m_defaultFont("ComicSans", 20) { setShapeId(ArtisticTextShapeID); cacheGlyphOutlines(); updateSizeAndPosition(); } ArtisticTextShape::~ArtisticTextShape() { if (m_path) { m_path->removeDependee(this); } } KoShape *ArtisticTextShape::cloneShape() const { ArtisticTextShape *clone = new ArtisticTextShape(); clone->m_ranges = m_ranges; if (m_path) { clone->m_path = static_cast(m_path->cloneShape()); } clone->m_charOutlines = m_charOutlines; clone->m_startOffset = m_startOffset; clone->m_outlineOrigin = m_outlineOrigin; clone->m_outline = m_outline; clone->m_baseline = m_baseline; clone->m_textAnchor = m_textAnchor; clone->m_charOffsets = m_charOffsets; clone->m_charPositions = m_charPositions; clone->m_textUpdateCounter = m_textUpdateCounter; clone->m_defaultFont = m_defaultFont; return clone; } void ArtisticTextShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); applyConversion(painter, converter); if (background()) { background()->paint(painter, converter, paintContext, outline()); } } void ArtisticTextShape::saveOdf(KoShapeSavingContext &context) const { SvgWriter svgWriter(QList() << const_cast(this)); QByteArray fileContent; QBuffer fileContentDevice(&fileContent); if (!fileContentDevice.open(QIODevice::WriteOnly)) { return; } if (!svgWriter.save(fileContentDevice, size())) { qWarning() << "Could not write svg content"; return; } const QString fileName = context.embeddedSaver().getFilename("SvgImages/Image"); const QString mimeType = "image/svg+xml"; context.xmlWriter().startElement("draw:frame"); context.embeddedSaver().embedFile(context.xmlWriter(), "draw:image", fileName, mimeType.toLatin1(), fileContent); context.xmlWriter().endElement(); // draw:frame } bool ArtisticTextShape::loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/) { return false; } QSizeF ArtisticTextShape::size() const { if (m_ranges.isEmpty()) { return nullBoundBox().size(); } else { return outline().boundingRect().size(); } } void ArtisticTextShape::setSize(const QSizeF &newSize) { QSizeF oldSize = size(); if (!oldSize.isNull()) { qreal zoomX = newSize.width() / oldSize.width(); qreal zoomY = newSize.height() / oldSize.height(); QTransform matrix(zoomX, 0, 0, zoomY, 0, 0); update(); applyTransformation(matrix); update(); } KoShape::setSize(newSize); } QPainterPath ArtisticTextShape::outline() const { return m_outline; } QRectF ArtisticTextShape::nullBoundBox() const { QFontMetrics metrics(defaultFont()); QPointF tl(0.0, -metrics.ascent()); QPointF br(metrics.averageCharWidth(), metrics.descent()); return QRectF(tl, br); } QFont ArtisticTextShape::defaultFont() const { return m_defaultFont; } qreal baselineShiftForFontSize(const ArtisticTextRange &range, qreal fontSize) { switch (range.baselineShift()) { case ArtisticTextRange::Sub: return fontSize / 3.; // taken from wikipedia case ArtisticTextRange::Super: return -fontSize / 3.; // taken from wikipedia case ArtisticTextRange::Percent: return range.baselineShiftValue() * fontSize; case ArtisticTextRange::Length: return range.baselineShiftValue(); default: return 0.0; } } QVector ArtisticTextShape::calculateAbstractCharacterPositions() { const int totalTextLength = plainText().length(); QVector charPositions; // one more than the number of characters for position after the last character charPositions.resize(totalTextLength + 1); // the character index within the text shape int globalCharIndex = 0; QPointF charPos(0, 0); QPointF advance(0, 0); const bool attachedToPath = isOnPath(); Q_FOREACH (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); const QString textRange = range.text(); const qreal letterSpacing = range.letterSpacing(); const int localTextLength = textRange.length(); const bool absoluteXOffset = range.xOffsetType() == ArtisticTextRange::AbsoluteOffset; const bool absoluteYOffset = range.yOffsetType() == ArtisticTextRange::AbsoluteOffset; // set baseline shift const qreal baselineShift = baselineShiftForFontSize(range, defaultFont().pointSizeF()); for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) { // apply offset to character if (range.hasXOffset(localCharIndex)) { if (absoluteXOffset) { charPos.rx() = range.xOffset(localCharIndex); } else { charPos.rx() += range.xOffset(localCharIndex); } } else { charPos.rx() += advance.x(); } if (range.hasYOffset(localCharIndex)) { if (absoluteYOffset) { // when attached to a path, absolute y-offsets are ignored if (!attachedToPath) { charPos.ry() = range.yOffset(localCharIndex); } } else { charPos.ry() += range.yOffset(localCharIndex); } } else { charPos.ry() += advance.y(); } // apply baseline shift charPos.ry() += baselineShift; // save character position of current character charPositions[globalCharIndex] = charPos; // advance character position advance = QPointF(metrics.width(textRange[localCharIndex]) + letterSpacing, 0.0); charPos.ry() -= baselineShift; } } charPositions[globalCharIndex] = charPos + advance; return charPositions; } void ArtisticTextShape::createOutline() { // reset relevant data m_outline = QPainterPath(); m_charPositions.clear(); m_charOffsets.clear(); // calculate character positions in baseline coordinates m_charPositions = calculateAbstractCharacterPositions(); // the character index within the text shape int globalCharIndex = 0; if (isOnPath()) { // one more than the number of characters for offset after the last character m_charOffsets.insert(0, m_charPositions.size(), -1); // the current character position qreal startCharOffset = m_startOffset * m_baseline.length(); // calculate total text width qreal totalTextWidth = 0.0; foreach (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); totalTextWidth += metrics.width(range.text()); } // adjust starting character position to anchor point if (m_textAnchor == AnchorMiddle) { startCharOffset -= 0.5 * totalTextWidth; } else if (m_textAnchor == AnchorEnd) { startCharOffset -= totalTextWidth; } QPointF pathPoint; qreal rotation = 0.0; qreal charOffset; foreach (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); const QString localText = range.text(); const int localTextLength = localText.length(); for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) { QPointF charPos = m_charPositions[globalCharIndex]; // apply advance along baseline charOffset = startCharOffset + charPos.x(); const qreal charMidPoint = charOffset + 0.5 * metrics.width(localText[localCharIndex]); // get the normalized position of the middle of the character const qreal midT = m_baseline.percentAtLength(charMidPoint); // is the character midpoint beyond the baseline ends? if (midT <= 0.0 || midT >= 1.0) { if (midT >= 1.0) { pathPoint = m_baseline.pointAtPercent(1.0); for (int i = globalCharIndex; i < m_charPositions.size(); ++i) { m_charPositions[i] = pathPoint; m_charOffsets[i] = 1.0; } break; } else { m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(0.0); m_charOffsets[globalCharIndex] = 0.0; continue; } } // get the percent value of the actual char position qreal t = m_baseline.percentAtLength(charOffset); // get the path point of the given path position pathPoint = m_baseline.pointAtPercent(t); // save character offset as fraction of baseline length m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(charOffset); // save character position as point m_charPositions[globalCharIndex] = pathPoint; // get the angle at the given path position const qreal angle = m_baseline.angleAtPercent(midT); if (range.hasRotation(localCharIndex)) { rotation = range.rotation(localCharIndex); } QTransform m; m.translate(pathPoint.x(), pathPoint.y()); m.rotate(360. - angle + rotation); m.translate(0.0, charPos.y()); m_outline.addPath(m.map(m_charOutlines[globalCharIndex])); } } // save offset and position after last character m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(startCharOffset + m_charPositions[globalCharIndex].x()); m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(m_charOffsets[globalCharIndex]); } else { qreal rotation = 0.0; Q_FOREACH (const ArtisticTextRange &range, m_ranges) { const QString textRange = range.text(); const int localTextLength = textRange.length(); for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) { const QPointF &charPos = m_charPositions[globalCharIndex]; if (range.hasRotation(localCharIndex)) { rotation = range.rotation(localCharIndex); } QTransform m; m.translate(charPos.x(), charPos.y()); m.rotate(rotation); m_outline.addPath(m.map(m_charOutlines[globalCharIndex])); } } } } void ArtisticTextShape::setPlainText(const QString &newText) { if (plainText() == newText) { return; } beginTextUpdate(); if (newText.isEmpty()) { // remove all text ranges m_ranges.clear(); } else if (isEmpty()) { // create new text range m_ranges.append(ArtisticTextRange(newText, defaultFont())); } else { // set text to first range m_ranges.first().setText(newText); // remove all ranges except the first while (m_ranges.count() > 1) { m_ranges.pop_back(); } } finishTextUpdate(); } QString ArtisticTextShape::plainText() const { QString allText; Q_FOREACH (const ArtisticTextRange &range, m_ranges) { allText += range.text(); } return allText; } QList ArtisticTextShape::text() const { return m_ranges; } bool ArtisticTextShape::isEmpty() const { return m_ranges.isEmpty(); } void ArtisticTextShape::clear() { beginTextUpdate(); m_ranges.clear(); finishTextUpdate(); } void ArtisticTextShape::setFont(const QFont &newFont) { // no text if (isEmpty()) { return; } const int rangeCount = m_ranges.count(); // only one text range with the same font if (rangeCount == 1 && m_ranges.first().font() == newFont) { return; } beginTextUpdate(); // set font on ranges for (int i = 0; i < rangeCount; ++i) { m_ranges[i].setFont(newFont); } m_defaultFont = newFont; finishTextUpdate(); } void ArtisticTextShape::setFont(int charIndex, int charCount, const QFont &font) { if (isEmpty() || charCount <= 0) { return; } if (charIndex == 0 && charCount == plainText().length()) { setFont(font); return; } CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || charPos.first >= m_ranges.count()) { return; } beginTextUpdate(); int remainingCharCount = charCount; while (remainingCharCount > 0) { ArtisticTextRange &currRange = m_ranges[charPos.first]; // does this range have a different font ? if (currRange.font() != font) { if (charPos.second == 0 && currRange.text().length() < remainingCharCount) { // set font on all characters of this range currRange.setFont(font); remainingCharCount -= currRange.text().length(); } else { ArtisticTextRange changedRange = currRange.extract(charPos.second, remainingCharCount); changedRange.setFont(font); if (charPos.second == 0) { m_ranges.insert(charPos.first, changedRange); } else if (charPos.second >= currRange.text().length()) { m_ranges.insert(charPos.first + 1, changedRange); } else { ArtisticTextRange remainingRange = currRange.extract(charPos.second); m_ranges.insert(charPos.first + 1, changedRange); m_ranges.insert(charPos.first + 2, remainingRange); } charPos.first++; remainingCharCount -= changedRange.text().length(); } } charPos.first++; if (charPos.first >= m_ranges.count()) { break; } charPos.second = 0; } finishTextUpdate(); } QFont ArtisticTextShape::fontAt(int charIndex) const { if (isEmpty()) { return defaultFont(); } if (charIndex < 0) { return m_ranges.first().font(); } const int rangeIndex = indexOfChar(charIndex).first; if (rangeIndex < 0) { return m_ranges.last().font(); } return m_ranges[rangeIndex].font(); } void ArtisticTextShape::setStartOffset(qreal offset) { if (m_startOffset == offset) { return; } update(); m_startOffset = qBound(0.0, offset, 1.0); updateSizeAndPosition(); update(); notifyChanged(); } qreal ArtisticTextShape::startOffset() const { return m_startOffset; } qreal ArtisticTextShape::baselineOffset() const { return m_charPositions.value(0).y(); } void ArtisticTextShape::setTextAnchor(TextAnchor anchor) { if (anchor == m_textAnchor) { return; } qreal totalTextWidth = 0.0; foreach (const ArtisticTextRange &range, m_ranges) { QFontMetricsF metrics(QFont(range.font(), &m_paintDevice)); totalTextWidth += metrics.width(range.text()); } qreal oldOffset = 0.0; if (m_textAnchor == AnchorMiddle) { oldOffset = -0.5 * totalTextWidth; } else if (m_textAnchor == AnchorEnd) { oldOffset = -totalTextWidth; } m_textAnchor = anchor; qreal newOffset = 0.0; if (m_textAnchor == AnchorMiddle) { newOffset = -0.5 * totalTextWidth; } else if (m_textAnchor == AnchorEnd) { newOffset = -totalTextWidth; } update(); updateSizeAndPosition(); if (! isOnPath()) { QTransform m; m.translate(newOffset - oldOffset, 0.0); setTransformation(transformation() * m); } update(); notifyChanged(); } ArtisticTextShape::TextAnchor ArtisticTextShape::textAnchor() const { return m_textAnchor; } bool ArtisticTextShape::putOnPath(KoPathShape *path) { if (! path) { return false; } if (path->outline().isEmpty()) { return false; } if (! path->addDependee(this)) { return false; } update(); m_path = path; // use the paths outline converted to document coordinates as the baseline m_baseline = m_path->absoluteTransformation(0).map(m_path->outline()); // reset transformation setTransformation(QTransform()); updateSizeAndPosition(); // move to correct position setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeft); update(); return true; } bool ArtisticTextShape::putOnPath(const QPainterPath &path) { if (path.isEmpty()) { return false; } update(); if (m_path) { m_path->removeDependee(this); } m_path = 0; m_baseline = path; // reset transformation setTransformation(QTransform()); updateSizeAndPosition(); // move to correct position setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeft); update(); return true; } void ArtisticTextShape::removeFromPath() { update(); if (m_path) { m_path->removeDependee(this); } m_path = 0; m_baseline = QPainterPath(); updateSizeAndPosition(); update(); } bool ArtisticTextShape::isOnPath() const { return (m_path != 0 || ! m_baseline.isEmpty()); } ArtisticTextShape::LayoutMode ArtisticTextShape::layout() const { if (m_path) { return OnPathShape; } else if (! m_baseline.isEmpty()) { return OnPath; } else { return Straight; } } QPainterPath ArtisticTextShape::baseline() const { return m_baseline; } KoPathShape *ArtisticTextShape::baselineShape() const { return m_path; } QList ArtisticTextShape::removeText(int charIndex, int charCount) { QList extractedRanges; if (!charCount) { return extractedRanges; } if (charIndex == 0 && charCount >= plainText().length()) { beginTextUpdate(); extractedRanges = m_ranges; m_ranges.clear(); finishTextUpdate(); return extractedRanges; } CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || charPos.first >= m_ranges.count()) { return extractedRanges; } beginTextUpdate(); int extractedTextLength = 0; while (extractedTextLength < charCount) { ArtisticTextRange r = m_ranges[charPos.first].extract(charPos.second, charCount - extractedTextLength); extractedTextLength += r.text().length(); extractedRanges.append(r); if (extractedTextLength == charCount) { break; } charPos.first++; if (charPos.first >= m_ranges.count()) { break; } charPos.second = 0; } // now remove all empty ranges const int rangeCount = m_ranges.count(); for (int i = charPos.first; i < rangeCount; ++i) { if (m_ranges[charPos.first].text().isEmpty()) { m_ranges.removeAt(charPos.first); } } finishTextUpdate(); return extractedRanges; } QList ArtisticTextShape::copyText(int charIndex, int charCount) { QList extractedRanges; if (!charCount) { return extractedRanges; } CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || charPos.first >= m_ranges.count()) { return extractedRanges; } int extractedTextLength = 0; while (extractedTextLength < charCount) { ArtisticTextRange copy = m_ranges[charPos.first]; ArtisticTextRange r = copy.extract(charPos.second, charCount - extractedTextLength); extractedTextLength += r.text().length(); extractedRanges.append(r); if (extractedTextLength == charCount) { break; } charPos.first++; if (charPos.first >= m_ranges.count()) { break; } charPos.second = 0; } return extractedRanges; } void ArtisticTextShape::insertText(int charIndex, const QString &str) { if (isEmpty()) { appendText(str); return; } CharIndex charPos = indexOfChar(charIndex); if (charIndex < 0) { // insert before first character charPos = CharIndex(0, 0); } else if (charIndex >= plainText().length()) { // insert after last character charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length()); } // check range index, just in case if (charPos.first < 0) { return; } beginTextUpdate(); m_ranges[charPos.first].insertText(charPos.second, str); finishTextUpdate(); } void ArtisticTextShape::insertText(int charIndex, const ArtisticTextRange &textRange) { QList ranges; ranges.append(textRange); insertText(charIndex, ranges); } void ArtisticTextShape::insertText(int charIndex, const QList &textRanges) { if (isEmpty()) { beginTextUpdate(); m_ranges = textRanges; finishTextUpdate(); return; } CharIndex charPos = indexOfChar(charIndex); if (charIndex < 0) { // insert before first character charPos = CharIndex(0, 0); } else if (charIndex >= plainText().length()) { // insert after last character charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length()); } // check range index, just in case if (charPos.first < 0) { return; } beginTextUpdate(); ArtisticTextRange &hitRange = m_ranges[charPos.first]; if (charPos.second == 0) { // insert ranges before the hit range Q_FOREACH (const ArtisticTextRange &range, textRanges) { m_ranges.insert(charPos.first, range); charPos.first++; } } else if (charPos.second == hitRange.text().length()) { // insert ranges after the hit range Q_FOREACH (const ArtisticTextRange &range, textRanges) { m_ranges.insert(charPos.first + 1, range); charPos.first++; } } else { // insert ranges inside hit range ArtisticTextRange right = hitRange.extract(charPos.second, hitRange.text().length()); m_ranges.insert(charPos.first + 1, right); // now insert after the left part of hit range Q_FOREACH (const ArtisticTextRange &range, textRanges) { m_ranges.insert(charPos.first + 1, range); charPos.first++; } } // TODO: merge ranges with same style finishTextUpdate(); } void ArtisticTextShape::appendText(const QString &text) { beginTextUpdate(); if (isEmpty()) { m_ranges.append(ArtisticTextRange(text, defaultFont())); } else { m_ranges.last().appendText(text); } finishTextUpdate(); } void ArtisticTextShape::appendText(const ArtisticTextRange &text) { beginTextUpdate(); m_ranges.append(text); // TODO: merge ranges with same style finishTextUpdate(); } bool ArtisticTextShape::replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange) { QList ranges; ranges.append(textRange); return replaceText(charIndex, charCount, ranges); } bool ArtisticTextShape::replaceText(int charIndex, int charCount, const QList &textRanges) { CharIndex charPos = indexOfChar(charIndex); if (charPos.first < 0 || !charCount) { return false; } beginTextUpdate(); removeText(charIndex, charCount); insertText(charIndex, textRanges); finishTextUpdate(); return true; } qreal ArtisticTextShape::charAngleAt(int charIndex) const { if (isOnPath()) { qreal t = m_charOffsets.value(qBound(0, charIndex, m_charOffsets.size() - 1)); return m_baseline.angleAtPercent(t); } return 0.0; } QPointF ArtisticTextShape::charPositionAt(int charIndex) const { return m_charPositions.value(qBound(0, charIndex, m_charPositions.size() - 1)); } QRectF ArtisticTextShape::charExtentsAt(int charIndex) const { CharIndex charPos = indexOfChar(charIndex); if (charIndex < 0 || isEmpty()) { charPos = CharIndex(0, 0); } else if (charPos.first < 0) { charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length() - 1); } if (charPos.first < m_ranges.size()) { const ArtisticTextRange &range = m_ranges.at(charPos.first); QFontMetrics metrics(range.font()); int w = metrics.charWidth(range.text(), charPos.second); return QRectF(0, 0, w, metrics.height()); } return QRectF(); } void ArtisticTextShape::updateSizeAndPosition(bool global) { QTransform shapeTransform = absoluteTransformation(0); // determine baseline position in document coordinates QPointF oldBaselinePosition = shapeTransform.map(QPointF(0, baselineOffset())); createOutline(); QRectF bbox = m_outline.boundingRect(); if (bbox.isEmpty()) { bbox = nullBoundBox(); } if (isOnPath()) { // calculate the offset we have to apply to keep our position QPointF offset = m_outlineOrigin - bbox.topLeft(); // cache topleft corner of baseline path m_outlineOrigin = bbox.topLeft(); // the outline position is in document coordinates // so we adjust our position QTransform m; m.translate(-offset.x(), -offset.y()); global ? applyAbsoluteTransformation(m) : applyTransformation(m); } else { // determine the new baseline position in document coordinates QPointF newBaselinePosition = shapeTransform.map(QPointF(0, -bbox.top())); // apply a transformation to compensate any translation of // our baseline position QPointF delta = oldBaselinePosition - newBaselinePosition; QTransform m; m.translate(delta.x(), delta.y()); applyAbsoluteTransformation(m); } setSize(bbox.size()); // map outline to shape coordinate system QTransform normalizeMatrix; normalizeMatrix.translate(-bbox.left(), -bbox.top()); m_outline = normalizeMatrix.map(m_outline); const int charCount = m_charPositions.count(); for (int i = 0; i < charCount; ++i) { m_charPositions[i] = normalizeMatrix.map(m_charPositions[i]); } } void ArtisticTextShape::cacheGlyphOutlines() { m_charOutlines.clear(); Q_FOREACH (const ArtisticTextRange &range, m_ranges) { const QString rangeText = range.text(); const QFont rangeFont(range.font(), &m_paintDevice); const int textLength = rangeText.length(); for (int charIdx = 0; charIdx < textLength; ++charIdx) { QPainterPath charOutline; charOutline.addText(QPointF(), rangeFont, rangeText[charIdx]); m_charOutlines.append(charOutline); } } } void ArtisticTextShape::shapeChanged(ChangeType type, KoShape *shape) { if (m_path && shape == m_path) { if (type == KoShape::Deleted) { // baseline shape was deleted m_path = 0; } else if (type == KoShape::ParentChanged && !shape->parent()) { // baseline shape was probably removed from the document m_path->removeDependee(this); m_path = 0; } else { update(); // use the paths outline converted to document coordinates as the baseline m_baseline = m_path->absoluteTransformation(0).map(m_path->outline()); updateSizeAndPosition(true); update(); } } } CharIndex ArtisticTextShape::indexOfChar(int charIndex) const { if (isEmpty()) { return CharIndex(-1, -1); } int rangeIndex = 0; int textLength = 0; Q_FOREACH (const ArtisticTextRange &range, m_ranges) { const int rangeTextLength = range.text().length(); if (static_cast(charIndex) < textLength + rangeTextLength) { return CharIndex(rangeIndex, charIndex - textLength); } textLength += rangeTextLength; rangeIndex++; } return CharIndex(-1, -1); } void ArtisticTextShape::beginTextUpdate() { if (m_textUpdateCounter) { return; } m_textUpdateCounter++; update(); } void ArtisticTextShape::finishTextUpdate() { if (!m_textUpdateCounter) { return; } cacheGlyphOutlines(); updateSizeAndPosition(); update(); notifyChanged(); m_textUpdateCounter--; } bool ArtisticTextShape::saveSvg(SvgSavingContext &context) { context.shapeWriter().startElement("text", false); context.shapeWriter().addAttribute("id", context.getID(this)); SvgStyleWriter::saveSvgStyle(this, context); const QList formattedText = text(); // if we have only a single text range, save the font on the text element const bool hasSingleRange = formattedText.size() == 1; if (hasSingleRange) { saveSvgFont(formattedText.first().font(), context); } qreal anchorOffset = 0.0; if (textAnchor() == ArtisticTextShape::AnchorMiddle) { anchorOffset += 0.5 * this->size().width(); context.shapeWriter().addAttribute("text-anchor", "middle"); } else if (textAnchor() == ArtisticTextShape::AnchorEnd) { anchorOffset += this->size().width(); context.shapeWriter().addAttribute("text-anchor", "end"); } // check if we are set on a path if (layout() == ArtisticTextShape::Straight) { - context.shapeWriter().addAttributePt("x", anchorOffset); - context.shapeWriter().addAttributePt("y", baselineOffset()); + context.shapeWriter().addAttribute("x", anchorOffset); + context.shapeWriter().addAttribute("y", baselineOffset()); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); Q_FOREACH (const ArtisticTextRange &range, formattedText) { saveSvgTextRange(range, context, !hasSingleRange, baselineOffset()); } } else { KoPathShape *baselineShape = KoPathShape::createShapeFromPainterPath(baseline()); QString id = context.createUID("baseline"); context.styleWriter().startElement("path"); context.styleWriter().addAttribute("id", id); context.styleWriter().addAttribute("d", baselineShape->toString(baselineShape->absoluteTransformation(0) * context.userSpaceTransform())); context.styleWriter().endElement(); context.shapeWriter().startElement("textPath"); context.shapeWriter().addAttribute("xlink:href", QLatin1Char('#') + id); if (startOffset() > 0.0) { context.shapeWriter().addAttribute("startOffset", QString("%1%").arg(startOffset() * 100.0)); } Q_FOREACH (const ArtisticTextRange &range, formattedText) { saveSvgTextRange(range, context, !hasSingleRange, baselineOffset()); } context.shapeWriter().endElement(); delete baselineShape; } context.shapeWriter().endElement(); return true; } void ArtisticTextShape::saveSvgFont(const QFont &font, SvgSavingContext &context) { context.shapeWriter().addAttribute("font-family", font.family()); - context.shapeWriter().addAttributePt("font-size", font.pointSizeF()); + context.shapeWriter().addAttribute("font-size", font.pointSizeF()); if (font.bold()) { context.shapeWriter().addAttribute("font-weight", "bold"); } if (font.italic()) { context.shapeWriter().addAttribute("font-style", "italic"); } } void ArtisticTextShape::saveSvgTextRange(const ArtisticTextRange &range, SvgSavingContext &context, bool saveRangeFont, qreal baselineOffset) { context.shapeWriter().startElement("tspan", false); if (range.hasXOffsets()) { const char *attributeName = (range.xOffsetType() == ArtisticTextRange::AbsoluteOffset ? "x" : "dx"); QString attributeValue; int charIndex = 0; while (range.hasXOffset(charIndex)) { if (charIndex) { attributeValue += QLatin1Char(','); } attributeValue += QString("%1").arg(SvgUtil::toUserSpace(range.xOffset(charIndex++))); } context.shapeWriter().addAttribute(attributeName, attributeValue); } if (range.hasYOffsets()) { if (range.yOffsetType() != ArtisticTextRange::AbsoluteOffset) { baselineOffset = 0; } const char *attributeName = (range.yOffsetType() == ArtisticTextRange::AbsoluteOffset ? " y" : " dy"); QString attributeValue; int charIndex = 0; while (range.hasYOffset(charIndex)) { if (charIndex) { attributeValue += QLatin1Char(','); } attributeValue += QString("%1").arg(SvgUtil::toUserSpace(baselineOffset + range.yOffset(charIndex++))); } context.shapeWriter().addAttribute(attributeName, attributeValue); } if (range.hasRotations()) { QString attributeValue; int charIndex = 0; while (range.hasRotation(charIndex)) { if (charIndex) { attributeValue += ','; } attributeValue += QString("%1").arg(range.rotation(charIndex++)); } context.shapeWriter().addAttribute("rotate", attributeValue); } if (range.baselineShift() != ArtisticTextRange::None) { switch (range.baselineShift()) { case ArtisticTextRange::Sub: context.shapeWriter().addAttribute("baseline-shift", "sub"); break; case ArtisticTextRange::Super: context.shapeWriter().addAttribute("baseline-shift", "super"); break; case ArtisticTextRange::Percent: context.shapeWriter().addAttribute("baseline-shift", QString("%1%").arg(range.baselineShiftValue() * 100)); break; case ArtisticTextRange::Length: context.shapeWriter().addAttribute("baseline-shift", QString("%1%").arg(SvgUtil::toUserSpace(range.baselineShiftValue()))); break; default: break; } } if (saveRangeFont) { saveSvgFont(range.font(), context); } context.shapeWriter().addTextNode(range.text()); context.shapeWriter().endElement(); } bool ArtisticTextShape::loadSvg(const KoXmlElement &textElement, SvgLoadingContext &context) { clear(); QString anchor; if (!textElement.attribute("text-anchor").isEmpty()) { anchor = textElement.attribute("text-anchor"); } SvgStyles elementStyles = context.styleParser().collectStyles(textElement); context.styleParser().parseFont(elementStyles); ArtisticTextLoadingContext textContext; textContext.parseCharacterTransforms(textElement, context.currentGC()); KoXmlElement parentElement = textElement; // first check if we have a "textPath" child element for (KoXmlNode n = textElement.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement e = n.toElement(); if (e.tagName() == "textPath") { parentElement = e; break; } } KoPathShape *path = 0; bool pathInDocument = false; double offset = 0.0; const bool hasTextPathElement = parentElement != textElement && parentElement.hasAttribute("xlink:href"); if (hasTextPathElement) { // create the referenced path shape context.pushGraphicsContext(parentElement); context.styleParser().parseFont(context.styleParser().collectStyles(parentElement)); textContext.pushCharacterTransforms(); textContext.parseCharacterTransforms(parentElement, context.currentGC()); QString href = parentElement.attribute("xlink:href").mid(1); if (context.hasDefinition(href)) { const KoXmlElement &p = context.definition(href); // must be a path element as per svg spec if (p.tagName() == "path") { pathInDocument = false; path = new KoPathShape(); path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(p.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); path->applyAbsoluteTransformation(context.currentGC()->matrix); } } else { path = dynamic_cast(context.shapeById(href)); if (path) { pathInDocument = true; } } // parse the start offset if (! parentElement.attribute("startOffset").isEmpty()) { QString start = parentElement.attribute("startOffset"); if (start.endsWith('%')) { offset = 0.01 * start.remove('%').toDouble(); } else { const float pathLength = path ? path->outline().length() : 0.0; if (pathLength > 0.0) { offset = start.toDouble() / pathLength; } } } } if (parentElement.hasChildNodes()) { // parse child elements parseTextRanges(parentElement, context, textContext); if (!context.currentGC()->preserveWhitespace) { const QString text = plainText(); if (text.endsWith(' ')) { removeText(text.length() - 1, 1); } } setPosition(textContext.textPosition()); } else { // a single text range appendText(createTextRange(textElement.text(), textContext, context.currentGC())); setPosition(textContext.textPosition()); } if (hasTextPathElement) { if (path) { if (pathInDocument) { putOnPath(path); } else { putOnPath(path->absoluteTransformation(0).map(path->outline())); delete path; } if (offset > 0.0) { setStartOffset(offset); } } textContext.popCharacterTransforms(); context.popGraphicsContext(); } // adjust position by baseline offset if (! isOnPath()) { setPosition(position() - QPointF(0, baselineOffset())); } if (anchor == "middle") { setTextAnchor(ArtisticTextShape::AnchorMiddle); } else if (anchor == "end") { setTextAnchor(ArtisticTextShape::AnchorEnd); } return true; } void ArtisticTextShape::parseTextRanges(const KoXmlElement &element, SvgLoadingContext &context, ArtisticTextLoadingContext &textContext) { for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement e = n.toElement(); if (e.isNull()) { ArtisticTextRange range = createTextRange(n.toText().data(), textContext, context.currentGC()); appendText(range); } else if (e.tagName() == "tspan") { SvgGraphicsContext *gc = context.pushGraphicsContext(e); context.styleParser().parseFont(context.styleParser().collectStyles(e)); textContext.pushCharacterTransforms(); textContext.parseCharacterTransforms(e, gc); parseTextRanges(e, context, textContext); textContext.popCharacterTransforms(); context.popGraphicsContext(); } else if (e.tagName() == "tref") { if (e.attribute("xlink:href").isEmpty()) { continue; } QString href = e.attribute("xlink:href").mid(1); ArtisticTextShape *refText = dynamic_cast(context.shapeById(href)); if (refText) { foreach (const ArtisticTextRange &range, refText->text()) { appendText(range); } } else if (context.hasDefinition(href)) { const KoXmlElement &p = context.definition(href); SvgGraphicsContext *gc = context.currentGC(); appendText(ArtisticTextRange(textContext.simplifyText(p.text(), gc->preserveWhitespace), gc->font)); } } else { continue; } } } ArtisticTextRange ArtisticTextShape::createTextRange(const QString &text, ArtisticTextLoadingContext &context, SvgGraphicsContext *gc) { ArtisticTextRange range(context.simplifyText(text, gc->preserveWhitespace), gc->font); const int textLength = range.text().length(); switch (context.xOffsetType()) { case ArtisticTextLoadingContext::Absolute: range.setXOffsets(context.xOffsets(textLength), ArtisticTextRange::AbsoluteOffset); break; case ArtisticTextLoadingContext::Relative: range.setXOffsets(context.xOffsets(textLength), ArtisticTextRange::RelativeOffset); break; default: // no x-offsets break; } switch (context.yOffsetType()) { case ArtisticTextLoadingContext::Absolute: range.setYOffsets(context.yOffsets(textLength), ArtisticTextRange::AbsoluteOffset); break; case ArtisticTextLoadingContext::Relative: range.setYOffsets(context.yOffsets(textLength), ArtisticTextRange::RelativeOffset); break; default: // no y-offsets break; } range.setRotations(context.rotations(textLength)); #if 0 range.setLetterSpacing(gc->letterSpacing); range.setWordSpacing(gc->wordSpacing); if (gc->baselineShift == "sub") { range.setBaselineShift(ArtisticTextRange::Sub); } else if (gc->baselineShift == "super") { range.setBaselineShift(ArtisticTextRange::Super); } else if (gc->baselineShift.endsWith('%')) { range.setBaselineShift(ArtisticTextRange::Percent, SvgUtil::fromPercentage(gc->baselineShift)); } else { qreal value = SvgUtil::parseUnitX(gc, gc->baselineShift); if (value != 0.0) { range.setBaselineShift(ArtisticTextRange::Length, value); } } #endif //range.printDebug(); return range; } diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp index eced3f1a67..30581fca92 100644 --- a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp +++ b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp @@ -1,554 +1,554 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006,2008 Jan Hambrecht Copyright (C) 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "EllipseShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include EllipseShape::EllipseShape() : m_startAngle(0) , m_endAngle(0) , m_kindAngle(M_PI) , m_type(Arc) { QList handles; handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(0, 50)); setHandles(handles); QSizeF size(100, 100); m_radii = QPointF(size.width() / 2.0, size.height() / 2.0); m_center = QPointF(m_radii.x(), m_radii.y()); updatePath(size); } EllipseShape::EllipseShape(const EllipseShape &rhs) : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), m_startAngle(rhs.m_startAngle), m_endAngle(rhs.m_endAngle), m_kindAngle(rhs.m_kindAngle), m_center(rhs.m_center), m_radii(rhs.m_radii), m_type(rhs.m_type) { } EllipseShape::~EllipseShape() { } KoShape *EllipseShape::cloneShape() const { return new EllipseShape(*this); } void EllipseShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:ellipse"); saveOdfAttributes(context, OdfAllAttributes); switch (m_type) { case Arc: context.xmlWriter().addAttribute("draw:kind", sweepAngle() == 360 ? "full" : "arc"); break; case Pie: context.xmlWriter().addAttribute("draw:kind", "section"); break; case Chord: context.xmlWriter().addAttribute("draw:kind", "cut"); break; default: context.xmlWriter().addAttribute("draw:kind", "full"); } if (m_type != Arc || sweepAngle() != 360) { context.xmlWriter().addAttribute("draw:start-angle", m_startAngle); context.xmlWriter().addAttribute("draw:end-angle", m_endAngle); } saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } else { KoPathShape::saveOdf(context); } } bool EllipseShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { QSizeF size; bool radiusGiven = true; QString kind = element.attributeNS(KoXmlNS::draw, "kind", "full"); if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx")); qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry")); size = QSizeF(2 * rx, 2 * ry); } else if (element.hasAttributeNS(KoXmlNS::svg, "r")) { qreal r = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "r")); size = QSizeF(2 * r, 2 * r); } else { size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); #ifndef NWORKAROUND_ODF_BUGS radiusGiven = KoOdfWorkaround::fixEllipse(kind, context); #else radiusGiven = false; #endif } setSize(size); QPointF pos; if (element.hasAttributeNS(KoXmlNS::svg, "cx") && element.hasAttributeNS(KoXmlNS::svg, "cy")) { qreal cx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cx")); qreal cy = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cy")); pos = QPointF(cx - 0.5 * size.width(), cy - 0.5 * size.height()); } else { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); if (kind == "section") { setType(Pie); } else if (kind == "cut") { setType(Chord); } else { setType(Arc); } setStartAngle(element.attributeNS(KoXmlNS::draw, "start-angle", "0").toDouble()); setEndAngle(element.attributeNS(KoXmlNS::draw, "end-angle", "360").toDouble()); if (!radiusGiven) { // is the size was given by width and height we have to reset the data as the size of the // part of the cut/pie is given. setSize(size); setPosition(pos); } loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements); loadText(element, context); return true; } void EllipseShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_center = matrix.map(m_center); m_radii = matrix.map(m_radii); KoParameterShape::setSize(newSize); } QPointF EllipseShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); m_center = matrix.map(m_center); return offset; } void EllipseShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); QPointF diff(m_center - point); diff.setX(-diff.x()); qreal angle = 0; if (diff.x() == 0) { angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0; } else { diff.setY(diff.y() * m_radii.x() / m_radii.y()); angle = atan(diff.y() / diff.x()); if (angle < 0) { angle += M_PI; } if (diff.y() < 0) { angle += M_PI; } } QList handles = this->handles(); switch (handleId) { case 0: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_startAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 1: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_endAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 2: { QList kindHandlePositions; kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()))); kindHandlePositions.push_back(m_center); kindHandlePositions.push_back((handles[0] + handles[1]) / 2.0); QPointF diff = m_center * 2.0; int handlePos = 0; for (int i = 0; i < kindHandlePositions.size(); ++i) { QPointF pointDiff(p - kindHandlePositions[i]); if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) { diff = pointDiff; handlePos = i; } } handles[handleId] = kindHandlePositions[handlePos]; m_type = EllipseType(handlePos); } break; } setHandles(handles); if (handleId != 2) { updateKindHandle(); } } void EllipseShape::updatePath(const QSizeF &size) { Q_D(KoParameterShape); Q_UNUSED(size); QPointF startpoint(handles()[0]); QPointF curvePoints[12]; const qreal distance = sweepAngle(); const bool sameAngles = distance > 359.9; int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, distance, startpoint, curvePoints); KIS_SAFE_ASSERT_RECOVER_RETURN(pointCnt); int curvePointCount = 1 + pointCnt / 3; int requiredPointCount = curvePointCount; if (m_type == Pie) { requiredPointCount++; } else if (m_type == Arc && sameAngles) { curvePointCount--; requiredPointCount--; } createPoints(requiredPointCount); KoSubpath &points = *d->subpaths[0]; int curveIndex = 0; points[0]->setPoint(startpoint); points[0]->removeControlPoint1(); points[0]->setProperty(KoPathPoint::StartSubpath); for (int i = 1; i < curvePointCount; ++i) { points[i - 1]->setControlPoint2(curvePoints[curveIndex++]); points[i]->setControlPoint1(curvePoints[curveIndex++]); points[i]->setPoint(curvePoints[curveIndex++]); points[i]->removeControlPoint2(); } if (m_type == Pie) { points[requiredPointCount - 1]->setPoint(m_center); points[requiredPointCount - 1]->removeControlPoint1(); points[requiredPointCount - 1]->removeControlPoint2(); } else if (m_type == Arc && sameAngles) { points[curvePointCount - 1]->setControlPoint2(curvePoints[curveIndex]); points[0]->setControlPoint1(curvePoints[++curveIndex]); } for (int i = 0; i < requiredPointCount; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } d->subpaths[0]->last()->setProperty(KoPathPoint::StopSubpath); if (m_type == Arc && !sameAngles) { d->subpaths[0]->first()->unsetProperty(KoPathPoint::CloseSubpath); d->subpaths[0]->last()->unsetProperty(KoPathPoint::CloseSubpath); } else { d->subpaths[0]->first()->setProperty(KoPathPoint::CloseSubpath); d->subpaths[0]->last()->setProperty(KoPathPoint::CloseSubpath); } notifyPointsChanged(); normalize(); } void EllipseShape::createPoints(int requiredPointCount) { Q_D(KoParameterShape); if (d->subpaths.count() != 1) { clear(); d->subpaths.append(new KoSubpath()); } int currentPointCount = d->subpaths[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { delete d->subpaths[0]->front(); d->subpaths[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { d->subpaths[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } void EllipseShape::updateKindHandle() { qreal angle = 0.5 * (m_startAngle + m_endAngle); if (m_startAngle > m_endAngle) { angle += 180.0; } m_kindAngle = normalizeAngle(kisDegreesToRadians(angle)); QList handles = this->handles(); switch (m_type) { case Arc: handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()); break; case Pie: handles[2] = m_center; break; case Chord: handles[2] = (handles[0] + handles[1]) / 2.0; break; } setHandles(handles); } void EllipseShape::updateAngleHandles() { qreal startRadian = kisDegreesToRadians(normalizeAngleDegrees(m_startAngle)); qreal endRadian = kisDegreesToRadians(normalizeAngleDegrees(m_endAngle)); QList handles = this->handles(); handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y()); handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y()); setHandles(handles); } qreal EllipseShape::sweepAngle() const { const qreal a1 = normalizeAngle(kisDegreesToRadians(m_startAngle)); const qreal a2 = normalizeAngle(kisDegreesToRadians(m_endAngle)); qreal sAngle = a2 - a1; if (a1 > a2) { sAngle = 2 * M_PI + sAngle; } if (qAbs(a1 - a2) < 0.05 / M_PI) { sAngle = 2 * M_PI; } return kisRadiansToDegrees(sAngle); } void EllipseShape::setType(EllipseType type) { m_type = type; updateKindHandle(); updatePath(size()); } EllipseShape::EllipseType EllipseShape::type() const { return m_type; } void EllipseShape::setStartAngle(qreal angle) { m_startAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::startAngle() const { return m_startAngle; } void EllipseShape::setEndAngle(qreal angle) { m_endAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::endAngle() const { return m_endAngle; } QString EllipseShape::pathShapeId() const { return EllipseShapeId; } bool EllipseShape::saveSvg(SvgSavingContext &context) { // let basic path saiving code handle our saving if (!isParametricShape()) return false; if (type() == EllipseShape::Arc && startAngle() == endAngle()) { const QSizeF size = this->size(); const bool isCircle = size.width() == size.height(); context.shapeWriter().startElement(isCircle ? "circle" : "ellipse"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); if (isCircle) { - context.shapeWriter().addAttributePt("r", 0.5 * size.width()); + context.shapeWriter().addAttribute("r", 0.5 * size.width()); } else { - context.shapeWriter().addAttributePt("rx", 0.5 * size.width()); - context.shapeWriter().addAttributePt("ry", 0.5 * size.height()); + context.shapeWriter().addAttribute("rx", 0.5 * size.width()); + context.shapeWriter().addAttribute("ry", 0.5 * size.height()); } - context.shapeWriter().addAttributePt("cx", 0.5 * size.width()); - context.shapeWriter().addAttributePt("cy", 0.5 * size.height()); + context.shapeWriter().addAttribute("cx", 0.5 * size.width()); + context.shapeWriter().addAttribute("cy", 0.5 * size.height()); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } else { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); context.shapeWriter().addAttribute("sodipodi:type", "arc"); - context.shapeWriter().addAttributePt("sodipodi:rx", m_radii.x()); - context.shapeWriter().addAttributePt("sodipodi:ry", m_radii.y()); + context.shapeWriter().addAttribute("sodipodi:rx", m_radii.x()); + context.shapeWriter().addAttribute("sodipodi:ry", m_radii.y()); - context.shapeWriter().addAttributePt("sodipodi:cx", m_center.x()); - context.shapeWriter().addAttributePt("sodipodi:cy", m_center.y()); + context.shapeWriter().addAttribute("sodipodi:cx", m_center.x()); + context.shapeWriter().addAttribute("sodipodi:cy", m_center.y()); context.shapeWriter().addAttribute("sodipodi:start", 2 * M_PI - kisDegreesToRadians(endAngle())); context.shapeWriter().addAttribute("sodipodi:end", 2 * M_PI - kisDegreesToRadians(startAngle())); switch (type()) { case Pie: // noop break; case Chord: context.shapeWriter().addAttribute("sodipodi:arc-type", "chord"); break; case Arc: context.shapeWriter().addAttribute("sodipodi:open", "true"); break; } context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform())); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } return true; } bool EllipseShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { qreal rx = 0, ry = 0; qreal cx = 0; qreal cy = 0; qreal start = 0; qreal end = 0; EllipseType type = Arc; const QString extendedNamespace = element.attribute("sodipodi:type") == "arc" ? "sodipodi" : element.attribute("krita:type") == "arc" ? "krita" : ""; if (element.tagName() == "ellipse") { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute("ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "circle") { rx = ry = SvgUtil::parseUnitXY(context.currentGC(), element.attribute("r")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "path" && !extendedNamespace.isEmpty()) { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":cy", "0")); start = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":end")); end = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":start")); const QString kritaArcType = element.attribute("sodipodi:arc-type", element.attribute("krita:arcType")); if (kritaArcType.isEmpty()) { if (element.attribute("sodipodi:open", "false") == "false") { type = Pie; } } else if (kritaArcType == "pie") { type = Pie; } else if (kritaArcType == "chord") { type = Chord; } } else { return false; } setSize(QSizeF(2 * rx, 2 * ry)); setPosition(QPointF(cx - rx, cy - ry)); if (rx == 0.0 || ry == 0.0) { setVisible(false); } if (start != 0 || start != end) { setStartAngle(kisRadiansToDegrees(start)); setEndAngle(kisRadiansToDegrees(end)); setType(type); } return true; } diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp index 625fa36f89..edbfc007a2 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp @@ -1,744 +1,744 @@ /* This file is part of the KDE project * Copyright (C) 2007,2010,2011 Jan Hambrecht * Copyright (C) 2009-2010 Thomas Zander * Copyright (C) 2010 Carlos Licea * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Contact: Suresh Chande suresh.chande@nokia.com * Copyright (C) 2009-2010 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "EnhancedPathShape.h" #include "EnhancedPathCommand.h" #include "EnhancedPathParameter.h" #include "EnhancedPathHandle.h" #include "EnhancedPathFormula.h" #include #include #include #include #include #include #include EnhancedPathShape::EnhancedPathShape(const QRect &viewBox) : m_viewBox(viewBox) , m_viewBoxOffset(0.0, 0.0) , m_mirrorVertically(false) , m_mirrorHorizontally(false) , m_pathStretchPointX(-1) , m_pathStretchPointY(-1) , m_cacheResults(false) { } EnhancedPathShape::EnhancedPathShape(const EnhancedPathShape &rhs) : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), m_viewBox(rhs.m_viewBox), m_viewBound(rhs.m_viewBound), m_viewMatrix(rhs.m_viewMatrix), m_mirrorMatrix(rhs.m_mirrorMatrix), m_viewBoxOffset(rhs.m_viewBoxOffset), m_textArea(rhs.m_textArea), m_commands(rhs.m_commands), m_enhancedHandles(rhs.m_enhancedHandles), m_formulae(rhs.m_formulae), m_modifiers(rhs.m_modifiers), m_parameters(rhs.m_parameters), m_mirrorVertically(rhs.m_mirrorVertically), m_mirrorHorizontally(rhs.m_mirrorHorizontally), m_pathStretchPointX(rhs.m_pathStretchPointX), m_pathStretchPointY(rhs.m_pathStretchPointY), m_resultChache(rhs.m_resultChache), m_cacheResults(rhs.m_cacheResults) { } EnhancedPathShape::~EnhancedPathShape() { reset(); } KoShape *EnhancedPathShape::cloneShape() const { return new EnhancedPathShape(*this); } void EnhancedPathShape::reset() { qDeleteAll(m_commands); m_commands.clear(); qDeleteAll(m_enhancedHandles); m_enhancedHandles.clear(); setHandles(QList()); qDeleteAll(m_formulae); m_formulae.clear(); qDeleteAll(m_parameters); m_parameters.clear(); m_modifiers.clear(); m_viewMatrix.reset(); m_viewBoxOffset = QPointF(); clear(); m_textArea.clear(); } void EnhancedPathShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); EnhancedPathHandle *handle = m_enhancedHandles[ handleId ]; if (handle) { handle->changePosition(shapeToViewbox(point)); } } void EnhancedPathShape::updatePath(const QSizeF &size) { Q_D(KoParameterShape); if (isParametricShape()) { clear(); enableResultCache(true); foreach (EnhancedPathCommand *cmd, m_commands) { cmd->execute(); } enableResultCache(false); qreal stretchPointsScale = 1; bool isStretched = useStretchPoints(size, stretchPointsScale); m_viewBound = outline().boundingRect(); m_mirrorMatrix.reset(); m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y()); m_mirrorMatrix.scale(m_mirrorHorizontally ? -1 : 1, m_mirrorVertically ? -1 : 1); m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y()); QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y()); // if stretch points are set than stretch the path manually if (isStretched) { //if the path was stretched manually the stretch matrix is not more valid //and it has to be recalculated so that stretching in x and y direction is the same matrix.scale(stretchPointsScale, stretchPointsScale); matrix = m_mirrorMatrix * matrix; } else { matrix = m_mirrorMatrix * m_viewMatrix * matrix; } foreach (KoSubpath *subpath, d->subpaths) { foreach (KoPathPoint *point, *subpath) { point->map(matrix); } } const int handleCount = m_enhancedHandles.count(); QList handles; for (int i = 0; i < handleCount; ++i) { handles.append(matrix.map(m_enhancedHandles[i]->position())); } setHandles(handles); normalize(); } } void EnhancedPathShape::setSize(const QSizeF &newSize) { // handle offset KoParameterShape::setSize(newSize); // calculate scaling factors from viewbox size to shape size qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width() / m_viewBound.width(); qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height() / m_viewBound.height(); // create view matrix, take mirroring into account m_viewMatrix.reset(); m_viewMatrix.scale(xScale, yScale); updatePath(newSize); } QPointF EnhancedPathShape::normalize() { QPointF offset = KoParameterShape::normalize(); m_viewBoxOffset -= offset; return offset; } QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const { return (m_mirrorMatrix * m_viewMatrix).inverted().map(point - m_viewBoxOffset); } void EnhancedPathShape::evaluateHandles() { const int handleCount = m_enhancedHandles.count(); QList handles; for (int i = 0; i < handleCount; ++i) { handles.append(m_enhancedHandles[i]->position()); } setHandles(handles); } QRect EnhancedPathShape::viewBox() const { return m_viewBox; } qreal EnhancedPathShape::evaluateReference(const QString &reference) { if (reference.isEmpty()) { return 0.0; } const char c = reference[0].toLatin1(); qreal res = 0.0; switch (c) { // referenced modifier case '$': { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); res = m_modifiers.value(modifierIndex); break; } // referenced formula case '?': { QString fname = reference.mid(1); if (m_cacheResults && m_resultChache.contains(fname)) { res = m_resultChache.value(fname); } else { FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname); if (formulaIt != m_formulae.constEnd()) { EnhancedPathFormula *formula = formulaIt.value(); if (formula) { res = formula->evaluate(); if (m_cacheResults) { m_resultChache.insert(fname, res); } } } } break; } // maybe an identifier ? default: EnhancedPathNamedParameter p(reference, this); res = p.evaluate(); break; } return res; } qreal EnhancedPathShape::evaluateConstantOrReference(const QString &val) { bool ok = true; qreal res = val.toDouble(&ok); if (ok) { return res; } return evaluateReference(val); } void EnhancedPathShape::modifyReference(const QString &reference, qreal value) { if (reference.isEmpty()) { return; } const char c = reference[0].toLatin1(); if (c == '$') { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) { m_modifiers[modifierIndex] = value; } } } EnhancedPathParameter *EnhancedPathShape::parameter(const QString &text) { Q_ASSERT(! text.isEmpty()); ParameterStore::const_iterator parameterIt = m_parameters.constFind(text); if (parameterIt != m_parameters.constEnd()) { return parameterIt.value(); } else { EnhancedPathParameter *parameter = 0; const char c = text[0].toLatin1(); if (c == '$' || c == '?') { parameter = new EnhancedPathReferenceParameter(text, this); } else { bool success = false; qreal constant = text.toDouble(&success); if (success) { parameter = new EnhancedPathConstantParameter(constant, this); } else { Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text); if (identifier != IdentifierUnknown) { parameter = new EnhancedPathNamedParameter(identifier, this); } } } if (parameter) { m_parameters[text] = parameter; } return parameter; } } void EnhancedPathShape::addFormula(const QString &name, const QString &formula) { if (name.isEmpty() || formula.isEmpty()) { return; } m_formulae[name] = new EnhancedPathFormula(formula, this); } void EnhancedPathShape::addHandle(const QMap &handle) { if (handle.isEmpty()) { return; } if (!handle.contains("draw:handle-position")) { return; } QVariant position = handle.value("draw:handle-position"); QStringList tokens = position.toString().simplified().split(' '); if (tokens.count() < 2) { return; } EnhancedPathHandle *newHandle = new EnhancedPathHandle(this); newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1])); // check if we have a polar handle if (handle.contains("draw:handle-polar")) { QVariant polar = handle.value("draw:handle-polar"); QStringList tokens = polar.toString().simplified().split(' '); if (tokens.count() == 2) { newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1])); QVariant minRadius = handle.value("draw:handle-radius-range-minimum"); QVariant maxRadius = handle.value("draw:handle-radius-range-maximum"); if (minRadius.isValid() && maxRadius.isValid()) { newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString())); } } } else { QVariant minX = handle.value("draw:handle-range-x-minimum"); QVariant maxX = handle.value("draw:handle-range-x-maximum"); if (minX.isValid() && maxX.isValid()) { newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString())); } QVariant minY = handle.value("draw:handle-range-y-minimum"); QVariant maxY = handle.value("draw:handle-range-y-maximum"); if (minY.isValid() && maxY.isValid()) { newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString())); } } m_enhancedHandles.append(newHandle); evaluateHandles(); } void EnhancedPathShape::addModifiers(const QString &modifiers) { if (modifiers.isEmpty()) { return; } QStringList tokens = modifiers.simplified().split(' '); int tokenCount = tokens.count(); for (int i = 0; i < tokenCount; ++i) { m_modifiers.append(tokens[i].toDouble()); } } void EnhancedPathShape::addCommand(const QString &command) { addCommand(command, true); } void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate) { QString commandStr = command.simplified(); if (commandStr.isEmpty()) { return; } // the first character is the command EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this); // strip command char commandStr = commandStr.mid(1).simplified(); // now parse the command parameters if (!commandStr.isEmpty()) { QStringList tokens = commandStr.split(' '); for (int i = 0; i < tokens.count(); ++i) { cmd->addParameter(parameter(tokens[i])); } } m_commands.append(cmd); if (triggerUpdate) { updatePath(size()); } } bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale) { Q_D(KoParameterShape); bool retval = false; if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) { qreal scaleX = size.width(); qreal scaleY = size.height(); if (qreal(m_viewBox.width()) / m_viewBox.height() < qreal(scaleX) / scaleY) { qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width(); foreach (KoSubpath *subpath, d->subpaths) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().x() >= m_pathStretchPointX && currPoint->controlPoint1().x() >= m_pathStretchPointX && currPoint->controlPoint2().x() >= m_pathStretchPointX) { currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y())); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX, currPoint->controlPoint1().y())); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX, currPoint->controlPoint2().y())); retval = true; } } } scale = scaleY / m_viewBox.height(); } else if (qreal(m_viewBox.width()) / m_viewBox.height() > qreal(scaleX) / scaleY) { qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height(); foreach (KoSubpath *subpath, d->subpaths) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().y() >= m_pathStretchPointY && currPoint->controlPoint1().y() >= m_pathStretchPointY && currPoint->controlPoint2().y() >= m_pathStretchPointY) { currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY)); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(), currPoint->controlPoint1().y() + deltaY)); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(), currPoint->controlPoint2().y() + deltaY)); retval = true; } } } scale = scaleX / m_viewBox.width(); } notifyPointsChanged(); } return retval; } void EnhancedPathShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:custom-shape"); const QSizeF currentSize = outline().boundingRect().size(); // save the right position so that when loading we fit the viewbox // to the right position without getting any wrong scaling // -> calculate the right position from the current 0 position / viewbound ratio // this is e.g. the case when there is a callout that goes into negative viewbound coordinates QPointF topLeft = m_viewBound.topLeft(); QPointF diff; if (qAbs(topLeft.x()) > 1E-5) { diff.setX(topLeft.x()*currentSize.width() / m_viewBound.width()); } if (qAbs(topLeft.y()) > 1E-5) { diff.setY(topLeft.y()*currentSize.height() / m_viewBound.height()); } if (diff.isNull()) { saveOdfAttributes(context, OdfAllAttributes & ~OdfSize); } else { //FIXME: this needs to be fixed for shapes that are transformed by rotation or skewing QTransform offset(context.shapeOffset(this)); QTransform newOffset(offset); newOffset.translate(-diff.x(), -diff.y()); context.addShapeOffset(this, newOffset); saveOdfAttributes(context, OdfAllAttributes & ~OdfSize); if (offset.isIdentity()) { context.removeShapeOffset(this); } else { context.addShapeOffset(this, offset); } } // save the right size so that when loading we fit the viewbox // to the right size without getting any wrong scaling // -> calculate the right size from the current size/viewbound ratio - context.xmlWriter().addAttributePt("svg:width", currentSize.width() == 0 ? 0 : m_viewBox.width()*currentSize.width() / m_viewBound.width()); - context.xmlWriter().addAttributePt("svg:height", currentSize.height() == 0 ? 0 : m_viewBox.height()*currentSize.height() / m_viewBound.height()); + context.xmlWriter().addAttribute("svg:width", currentSize.width() == 0 ? 0 : m_viewBox.width()*currentSize.width() / m_viewBound.width()); + context.xmlWriter().addAttribute("svg:height", currentSize.height() == 0 ? 0 : m_viewBox.height()*currentSize.height() / m_viewBound.height()); saveText(context); context.xmlWriter().startElement("draw:enhanced-geometry"); context.xmlWriter().addAttribute("svg:viewBox", QString("%1 %2 %3 %4").arg(m_viewBox.x()).arg(m_viewBox.y()).arg(m_viewBox.width()).arg(m_viewBox.height())); if (m_pathStretchPointX != -1) { context.xmlWriter().addAttribute("draw:path-stretchpoint-x", m_pathStretchPointX); } if (m_pathStretchPointY != -1) { context.xmlWriter().addAttribute("draw:path-stretchpoint-y", m_pathStretchPointY); } if (m_mirrorHorizontally) { context.xmlWriter().addAttribute("draw:mirror-horizontal", "true"); } if (m_mirrorVertically) { context.xmlWriter().addAttribute("draw:mirror-vertical", "true"); } QString modifiers; foreach (qreal modifier, m_modifiers) { modifiers += QString::number(modifier) + ' '; } context.xmlWriter().addAttribute("draw:modifiers", modifiers.trimmed()); if (m_textArea.size() >= 4) { context.xmlWriter().addAttribute("draw:text-areas", m_textArea.join(" ")); } QString path; foreach (EnhancedPathCommand *c, m_commands) { path += c->toString() + ' '; } context.xmlWriter().addAttribute("draw:enhanced-path", path.trimmed()); FormulaStore::const_iterator i = m_formulae.constBegin(); for (; i != m_formulae.constEnd(); ++i) { context.xmlWriter().startElement("draw:equation"); context.xmlWriter().addAttribute("draw:name", i.key()); context.xmlWriter().addAttribute("draw:formula", i.value()->toString()); context.xmlWriter().endElement(); // draw:equation } foreach (EnhancedPathHandle *handle, m_enhancedHandles) { handle->saveOdf(context); } context.xmlWriter().endElement(); // draw:enhanced-geometry saveOdfCommonChildElements(context); context.xmlWriter().endElement(); // draw:custom-shape } else { KoPathShape::saveOdf(context); } } bool EnhancedPathShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { reset(); const KoXmlElement enhancedGeometry(KoXml::namedItemNS(element, KoXmlNS::draw, "enhanced-geometry")); if (!enhancedGeometry.isNull()) { setPathStretchPointX(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-x", "-1").toDouble()); setPathStretchPointY(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-y", "-1").toDouble()); // load the modifiers QString modifiers = enhancedGeometry.attributeNS(KoXmlNS::draw, "modifiers", ""); if (!modifiers.isEmpty()) { addModifiers(modifiers); } m_textArea = enhancedGeometry.attributeNS(KoXmlNS::draw, "text-areas", "").split(' '); if (m_textArea.size() >= 4) { setResizeBehavior(TextFollowsPreferredTextRect); } KoXmlElement grandChild; forEachElement(grandChild, enhancedGeometry) { if (grandChild.namespaceURI() != KoXmlNS::draw) { continue; } if (grandChild.localName() == "equation") { QString name = grandChild.attributeNS(KoXmlNS::draw, "name"); QString formula = grandChild.attributeNS(KoXmlNS::draw, "formula"); addFormula(name, formula); } else if (grandChild.localName() == "handle") { EnhancedPathHandle *handle = new EnhancedPathHandle(this); if (handle->loadOdf(grandChild, context)) { m_enhancedHandles.append(handle); evaluateHandles(); } else { delete handle; } } } setMirrorHorizontally(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-horizontal") == "true"); setMirrorVertically(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-vertical") == "true"); // load the enhanced path data QString path = enhancedGeometry.attributeNS(KoXmlNS::draw, "enhanced-path", ""); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixEnhancedPath(path, enhancedGeometry, context); #endif // load the viewbox m_viewBox = loadOdfViewbox(enhancedGeometry); if (!path.isEmpty()) { parsePathData(path); } if (m_viewBox.isEmpty()) { // if there is no view box defined make it is big as the path. m_viewBox = m_viewBound.toAlignedRect(); } } QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // the viewbox is to be fitted into the size of the shape, so before setting // the size we just loaded // we set the viewbox to be the basis to calculate // the viewbox matrix from m_viewBound = m_viewBox; setSize(size); QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos - m_viewMatrix.map(QPointF(0, 0)) - m_viewBoxOffset); loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements); loadText(element, context); return true; } void EnhancedPathShape::parsePathData(const QString &data) { if (data.isEmpty()) { return; } int start = -1; bool separator = true; for (int i = 0; i < data.length(); ++i) { QChar ch = data.at(i); ushort uni_ch = ch.unicode(); if (separator && (uni_ch == 'M' || uni_ch == 'L' || uni_ch == 'C' || uni_ch == 'Z' || uni_ch == 'N' || uni_ch == 'F' || uni_ch == 'S' || uni_ch == 'T' || uni_ch == 'U' || uni_ch == 'A' || uni_ch == 'B' || uni_ch == 'W' || uni_ch == 'V' || uni_ch == 'X' || uni_ch == 'Y' || uni_ch == 'Q')) { if (start != -1) { // process last chars addCommand(data.mid(start, i - start), false); } start = i; } separator = ch.isSpace(); } if (start < data.length()) { addCommand(data.mid(start)); } if (start != -1) { updatePath(size()); } } void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally) { if (m_mirrorHorizontally != mirrorHorizontally) { m_mirrorHorizontally = mirrorHorizontally; updatePath(size()); } } void EnhancedPathShape::setMirrorVertically(bool mirrorVertically) { if (m_mirrorVertically != mirrorVertically) { m_mirrorVertically = mirrorVertically; updatePath(size()); } } void EnhancedPathShape::shapeChanged(ChangeType type, KoShape *shape) { KoParameterShape::shapeChanged(type, shape); if (!shape || shape == this) { if (type == ParentChanged || type == ParameterChanged) { updateTextArea(); } } } void EnhancedPathShape::updateTextArea() { if (m_textArea.size() >= 4) { QRectF r = m_viewBox; r.setLeft(evaluateConstantOrReference(m_textArea[0])); r.setTop(evaluateConstantOrReference(m_textArea[1])); r.setRight(evaluateConstantOrReference(m_textArea[2])); r.setBottom(evaluateConstantOrReference(m_textArea[3])); r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset); setPreferredTextRect(r); } } void EnhancedPathShape::enableResultCache(bool enable) { m_resultChache.clear(); m_cacheResults = enable; } void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX) { if (m_pathStretchPointX != pathStretchPointX) { m_pathStretchPointX = pathStretchPointX; } } void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY) { if (m_pathStretchPointY != pathStretchPointY) { m_pathStretchPointY = pathStretchPointY; } } diff --git a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp index c00c1383a9..3209bb7de7 100644 --- a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp +++ b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp @@ -1,389 +1,389 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2008 Jan Hambrecht Copyright (C) 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "RectangleShape.h" #include #include #include #include #include #include #include #include #include #include #include RectangleShape::RectangleShape() : m_cornerRadiusX(0) , m_cornerRadiusY(0) { QList handles; handles.push_back(QPointF(100, 0)); handles.push_back(QPointF(100, 0)); setHandles(handles); QSizeF size(100, 100); updatePath(size); } RectangleShape::RectangleShape(const RectangleShape &rhs) : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), m_cornerRadiusX(rhs.m_cornerRadiusX), m_cornerRadiusY(rhs.m_cornerRadiusY) { } RectangleShape::~RectangleShape() { } KoShape *RectangleShape::cloneShape() const { return new RectangleShape(*this); } bool RectangleShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfMandatories | OdfGeometry | OdfAdditionalAttributes | OdfCommonChildElements); if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx", "0")); qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry", "0")); m_cornerRadiusX = rx / (0.5 * size().width()) * 100; m_cornerRadiusY = ry / (0.5 * size().height()) * 100; } else { QString cornerRadius = element.attributeNS(KoXmlNS::draw, "corner-radius", ""); if (!cornerRadius.isEmpty()) { qreal radius = KoUnit::parseValue(cornerRadius); m_cornerRadiusX = qMin(radius / (0.5 * size().width()) * 100, qreal(100)); m_cornerRadiusY = qMin(radius / (0.5 * size().height()) * 100, qreal(100)); } } updatePath(size()); updateHandles(); loadOdfAttributes(element, context, OdfTransformation); loadText(element, context); return true; } void RectangleShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:rect"); saveOdfAttributes(context, OdfAllAttributes); if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) { - context.xmlWriter().addAttributePt("svg:rx", m_cornerRadiusX * (0.5 * size().width()) / 100.0); - context.xmlWriter().addAttributePt("svg:ry", m_cornerRadiusY * (0.5 * size().height()) / 100.0); + context.xmlWriter().addAttribute("svg:rx", m_cornerRadiusX * (0.5 * size().width()) / 100.0); + context.xmlWriter().addAttribute("svg:ry", m_cornerRadiusY * (0.5 * size().height()) / 100.0); } saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } else { KoPathShape::saveOdf(context); } } void RectangleShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); qreal width2 = size().width() / 2.0; qreal height2 = size().height() / 2.0; switch (handleId) { case 0: if (p.x() < width2) { p.setX(width2); } else if (p.x() > size().width()) { p.setX(size().width()); } p.setY(0); m_cornerRadiusX = (size().width() - p.x()) / width2 * 100.0; if (!(modifiers & Qt::ControlModifier)) { m_cornerRadiusY = (size().width() - p.x()) / height2 * 100.0; } break; case 1: if (p.y() < 0) { p.setY(0); } else if (p.y() > height2) { p.setY(height2); } p.setX(size().width()); m_cornerRadiusY = p.y() / height2 * 100.0; if (!(modifiers & Qt::ControlModifier)) { m_cornerRadiusX = p.y() / width2 * 100.0; } break; } // this is needed otherwise undo/redo might not end in the same result if (100 - m_cornerRadiusX < 1e-10) { m_cornerRadiusX = 100; } if (100 - m_cornerRadiusY < 1e-10) { m_cornerRadiusY = 100; } updateHandles(); } void RectangleShape::updateHandles() { QList handles; handles.append(QPointF(size().width() - m_cornerRadiusX / 100.0 * 0.5 * size().width(), 0.0)); handles.append(QPointF(size().width(), m_cornerRadiusY / 100.0 * 0.5 * size().height())); setHandles(handles); } void RectangleShape::updatePath(const QSizeF &size) { Q_D(KoParameterShape); qreal rx = 0; qreal ry = 0; if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) { rx = size.width() / 200.0 * m_cornerRadiusX; ry = size.height() / 200.0 * m_cornerRadiusY; } qreal x2 = size.width() - rx; qreal y2 = size.height() - ry; QPointF curvePoints[12]; int requiredCurvePointCount = 4; if (rx && m_cornerRadiusX < 100) { requiredCurvePointCount += 2; } if (ry && m_cornerRadiusY < 100) { requiredCurvePointCount += 2; } createPoints(requiredCurvePointCount); KoSubpath &points = *d->subpaths[0]; int cp = 0; // first path starts and closes path points[cp]->setProperty(KoPathPoint::StartSubpath); points[cp]->setProperty(KoPathPoint::CloseSubpath); points[cp]->setPoint(QPointF(rx, 0)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) { // end point of the top edge points[++cp]->setPoint(QPointF(x2, 0)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the top right radius arcToCurve(rx, ry, 90, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if (m_cornerRadiusY < 100 || m_cornerRadiusX == 0) { // the right edge points[++cp]->setPoint(QPointF(size.width(), y2)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the bottom right radius arcToCurve(rx, ry, 0, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) { // the bottom edge points[++cp]->setPoint(QPointF(rx, size.height())); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the bottom left radius arcToCurve(rx, ry, 270, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if ((m_cornerRadiusY < 100 || m_cornerRadiusX == 0) && ry) { // the right edge points[++cp]->setPoint(QPointF(0, ry)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the top left radius arcToCurve(rx, ry, 180, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[0]->setControlPoint1(curvePoints[1]); points[0]->setPoint(curvePoints[2]); } // unset all stop/close path properties for (int i = 1; i < cp; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } // last point stops and closes path points.last()->setProperty(KoPathPoint::StopSubpath); points.last()->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } void RectangleShape::createPoints(int requiredPointCount) { Q_D(KoParameterShape); if (d->subpaths.count() != 1) { clear(); d->subpaths.append(new KoSubpath()); } int currentPointCount = d->subpaths[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { delete d->subpaths[0]->front(); d->subpaths[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { d->subpaths[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } qreal RectangleShape::cornerRadiusX() const { return m_cornerRadiusX; } void RectangleShape::setCornerRadiusX(qreal radius) { if (radius >= 0.0 && radius <= 100.0) { m_cornerRadiusX = radius; updatePath(size()); updateHandles(); } } qreal RectangleShape::cornerRadiusY() const { return m_cornerRadiusY; } void RectangleShape::setCornerRadiusY(qreal radius) { if (radius >= 0.0 && radius <= 100.0) { m_cornerRadiusY = radius; updatePath(size()); updateHandles(); } } QString RectangleShape::pathShapeId() const { return RectangleShapeId; } bool RectangleShape::saveSvg(SvgSavingContext &context) { // let basic path saiving code handle our saving if (!isParametricShape()) return false; context.shapeWriter().startElement("rect"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); const QSizeF size = this->size(); - context.shapeWriter().addAttributePt("width", size.width()); - context.shapeWriter().addAttributePt("height", size.height()); + context.shapeWriter().addAttribute("width", size.width()); + context.shapeWriter().addAttribute("height", size.height()); double rx = cornerRadiusX(); if (rx > 0.0) { - context.shapeWriter().addAttributePt("rx", 0.01 * rx * 0.5 * size.width()); + context.shapeWriter().addAttribute("rx", 0.01 * rx * 0.5 * size.width()); } double ry = cornerRadiusY(); if (ry > 0.0) { - context.shapeWriter().addAttributePt("ry", 0.01 * ry * 0.5 * size.height()); + context.shapeWriter().addAttribute("ry", 0.01 * ry * 0.5 * size.height()); } context.shapeWriter().endElement(); return true; } bool RectangleShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x")); const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y")); const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width")); const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height")); const QString rxStr = element.attribute("rx"); const QString ryStr = element.attribute("ry"); qreal rx = rxStr.isEmpty() ? 0.0 : SvgUtil::parseUnitX(context.currentGC(), rxStr); qreal ry = ryStr.isEmpty() ? 0.0 : SvgUtil::parseUnitY(context.currentGC(), ryStr); // if one radius is given but not the other, use the same value for both if (!rxStr.isEmpty() && ryStr.isEmpty()) { ry = rx; } if (rxStr.isEmpty() && !ryStr.isEmpty()) { rx = ry; } setSize(QSizeF(w, h)); setPosition(QPointF(x, y)); if (rx >= 0.0) { setCornerRadiusX(qMin(qreal(100.0), qreal(rx / (0.5 * w) * 100.0))); } if (ry >= 0.0) { setCornerRadiusY(qMin(qreal(100.0), qreal(ry / (0.5 * h) * 100.0))); } if (w == 0.0 || h == 0.0) { setVisible(false); } return true; } diff --git a/plugins/flake/textshape/kotext/styles/KoListLevelProperties.cpp b/plugins/flake/textshape/kotext/styles/KoListLevelProperties.cpp index 287305702b..992e41ee05 100644 --- a/plugins/flake/textshape/kotext/styles/KoListLevelProperties.cpp +++ b/plugins/flake/textshape/kotext/styles/KoListLevelProperties.cpp @@ -1,938 +1,938 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2008 Pierre Ducroquet * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2010 Nandita Suri * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011-2012 Gopalakrishna Bhat A * Copyright (C) 2011 Mojtaba Shahi Senobari * * 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 "KoListLevelProperties.h" #include "KoTextSharedLoadingData.h" #include "Styles_p.h" #include #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoListLevelProperties::Private { public: StylePrivate stylesPrivate; void copy(Private *other) { stylesPrivate = other->stylesPrivate; } }; KoListLevelProperties::KoListLevelProperties() : QObject() , d(new Private()) { QSharedPointer charStyle(new KoCharacterStyle); setCharacterProperties(charStyle); setRelativeBulletSize(100); setAlignmentMode(false); setDisplayLevel(1); connect(this,SIGNAL(styleChanged(int)),SLOT(onStyleChanged(int))); } KoListLevelProperties::KoListLevelProperties(const KoListLevelProperties &other) : QObject() , d(new Private()) { d->copy(other.d); connect(this,SIGNAL(styleChanged(int)),SLOT(onStyleChanged(int))); } KoListLevelProperties::~KoListLevelProperties() { delete d; } int KoListLevelProperties::styleId() const { return propertyInt(KoListStyle::StyleId); } void KoListLevelProperties::setStyleId(int id) { setProperty(KoListStyle::StyleId, id); } void KoListLevelProperties::setProperty(int key, const QVariant &value) { d->stylesPrivate.add(key, value); } int KoListLevelProperties::propertyInt(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return 0; return variant.toInt(); } uint KoListLevelProperties::propertyUInt(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return 0; return variant.toUInt(); } qulonglong KoListLevelProperties::propertyULongLong(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return 0; return variant.toULongLong(); } qreal KoListLevelProperties::propertyDouble(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return 0.; return variant.toDouble(); } bool KoListLevelProperties::propertyBoolean(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return false; return variant.toBool(); } QString KoListLevelProperties::propertyString(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return QString(); return qvariant_cast(variant); } QColor KoListLevelProperties::propertyColor(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) return QColor(Qt::black); return qvariant_cast(variant); } QVariant KoListLevelProperties::property(int key) const { QVariant variant = d->stylesPrivate.value(key); if (!variant.isNull()) { return variant; } else { return QVariant(); } } void KoListLevelProperties::applyStyle(QTextListFormat &format) const { QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); format.setProperty(keys[i], variant); } } bool KoListLevelProperties::operator==(const KoListLevelProperties &other) const { return d->stylesPrivate == other.d->stylesPrivate; } bool KoListLevelProperties::operator!=(const KoListLevelProperties &other) const { return d->stylesPrivate != other.d->stylesPrivate; } void KoListLevelProperties::setListItemPrefix(const QString &prefix) { setProperty(KoListStyle::ListItemPrefix, prefix); } QString KoListLevelProperties::listItemPrefix() const { return propertyString(KoListStyle::ListItemPrefix); } void KoListLevelProperties::setStyle(KoListStyle::Style style) { setProperty(QTextListFormat::ListStyle, (int) style); emit styleChanged(style); } KoListStyle::Style KoListLevelProperties::style() const { return static_cast(propertyInt(QTextListFormat::ListStyle)); } void KoListLevelProperties::setListItemSuffix(const QString &suffix) { setProperty(KoListStyle::ListItemSuffix, suffix); } QString KoListLevelProperties::listItemSuffix() const { return propertyString(KoListStyle::ListItemSuffix); } void KoListLevelProperties::setStartValue(int value) { setProperty(KoListStyle::StartValue, value); } int KoListLevelProperties::startValue() const { return propertyInt(KoListStyle::StartValue); } void KoListLevelProperties::setLevel(int value) { setProperty(KoListStyle::Level, value); } int KoListLevelProperties::level() const { return propertyInt(KoListStyle::Level); } void KoListLevelProperties::setDisplayLevel(int level) { setProperty(KoListStyle::DisplayLevel, level); } int KoListLevelProperties::displayLevel() const { return propertyInt(KoListStyle::DisplayLevel); } void KoListLevelProperties::setCharacterStyleId(int id) { setProperty(KoListStyle::CharacterStyleId, id); } int KoListLevelProperties::characterStyleId() const { return propertyInt(KoListStyle::CharacterStyleId); } void KoListLevelProperties::setCharacterProperties(QSharedPointer< KoCharacterStyle > style) { setProperty(KoListStyle::CharacterProperties, QVariant::fromValue< QSharedPointer >(style)); } QSharedPointer KoListLevelProperties::characterProperties() const { const QVariant v = d->stylesPrivate.value(KoListStyle::CharacterProperties); if (v.isNull()) { return QSharedPointer(0); } return v.value< QSharedPointer >(); } void KoListLevelProperties::setBulletCharacter(QChar character) { setProperty(KoListStyle::BulletCharacter, (int) character.unicode()); } QChar KoListLevelProperties::bulletCharacter() const { return propertyInt(KoListStyle::BulletCharacter); } void KoListLevelProperties::setRelativeBulletSize(int percent) { setProperty(KoListStyle::RelativeBulletSize, percent); } int KoListLevelProperties::relativeBulletSize() const { return propertyInt(KoListStyle::RelativeBulletSize); } void KoListLevelProperties::setAlignment(Qt::Alignment align) { setProperty(KoListStyle::Alignment, static_cast(align)); } Qt::Alignment KoListLevelProperties::alignment() const { return static_cast(propertyInt(KoListStyle::Alignment)); } void KoListLevelProperties::setMinimumWidth(qreal width) { setProperty(KoListStyle::MinimumWidth, width); } qreal KoListLevelProperties::minimumWidth() const { return propertyDouble(KoListStyle::MinimumWidth); } void KoListLevelProperties::setWidth(qreal width) { setProperty(KoListStyle::Width, width); } qreal KoListLevelProperties::width() const { return propertyDouble(KoListStyle::Width); } void KoListLevelProperties::setHeight(qreal height) { setProperty(KoListStyle::Height, height); } qreal KoListLevelProperties::height() const { return propertyDouble(KoListStyle::Height); } void KoListLevelProperties::setBulletImage(KoImageData *imageData) { setProperty(KoListStyle::BulletImage, QVariant::fromValue(imageData)); } KoImageData *KoListLevelProperties::bulletImage() const { return property(KoListStyle::BulletImage).value< KoImageData * >(); } KoListLevelProperties & KoListLevelProperties::operator=(const KoListLevelProperties & other) { d->copy(other.d); return *this; } void KoListLevelProperties::setListId(KoListStyle::ListIdType listId) { setProperty(KoListStyle::ListId, listId); } KoListStyle::ListIdType KoListLevelProperties::listId() const { if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) return propertyUInt(KoListStyle::ListId); else return propertyULongLong(KoListStyle::ListId); } bool KoListLevelProperties::letterSynchronization() const { return propertyBoolean(KoListStyle::LetterSynchronization); } void KoListLevelProperties::setLetterSynchronization(bool on) { setProperty(KoListStyle::LetterSynchronization, on); } void KoListLevelProperties::setIndent(qreal value) { setProperty(KoListStyle::Indent, value); } qreal KoListLevelProperties::indent() const { return propertyDouble(KoListStyle::Indent); } void KoListLevelProperties::setMinimumDistance(qreal value) { setProperty(KoListStyle::MinimumDistance, value); } qreal KoListLevelProperties::minimumDistance() const { return propertyDouble(KoListStyle::MinimumDistance); } void KoListLevelProperties::setMargin(qreal value) { setProperty(KoListStyle::Margin, value); } qreal KoListLevelProperties::margin() const { return propertyDouble(KoListStyle::Margin); } void KoListLevelProperties::setTextIndent(qreal value) { setProperty(KoListStyle::TextIndent, value); } qreal KoListLevelProperties::textIndent() const { return propertyDouble(KoListStyle::TextIndent); } void KoListLevelProperties::setAlignmentMode(bool isLabelAlignmentMode) { setProperty(KoListStyle::AlignmentMode, isLabelAlignmentMode); } bool KoListLevelProperties::alignmentMode() const { return propertyBoolean(KoListStyle::AlignmentMode); } void KoListLevelProperties::setTabStopPosition(qreal value) { setProperty(KoListStyle::TabStopPosition,value); } qreal KoListLevelProperties::tabStopPosition() const { return propertyDouble(KoListStyle::TabStopPosition); } void KoListLevelProperties::setLabelFollowedBy(KoListStyle::ListLabelFollowedBy value) { setProperty(KoListStyle::LabelFollowedBy, value); } KoListStyle::ListLabelFollowedBy KoListLevelProperties::labelFollowedBy() const { return (KoListStyle::ListLabelFollowedBy)propertyInt(KoListStyle::LabelFollowedBy); } void KoListLevelProperties::setOutlineList(bool isOutline) { setProperty(KoListStyle::IsOutline, isOutline); } bool KoListLevelProperties::isOutlineList() const { return propertyBoolean(KoListStyle::IsOutline); } // static KoListLevelProperties KoListLevelProperties::fromTextList(QTextList *list) { KoListLevelProperties llp; if (!list) { llp.setStyle(KoListStyle::None); return llp; } llp.d->stylesPrivate = list->format().properties(); return llp; } void KoListLevelProperties::onStyleChanged(int key) { int bullet=KoListStyle::bulletCharacter(key); if (bullet != 0) setBulletCharacter(QChar(bullet)); //for numbered list the relative bullet size is made 100 if (KoListStyle::isNumberingStyle(key)) { setRelativeBulletSize(100); } } void KoListLevelProperties::loadOdf(KoShapeLoadingContext& scontext, const KoXmlElement& style) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); // The text:level attribute specifies the level of the number list // style. It can be used on all list-level styles. const int level = qMax(1, style.attributeNS(KoXmlNS::text, "level", QString()).toInt()); // The text:display-levels attribute specifies the number of // levels whose numbers are displayed at the current level. const QString displayLevel = style.attributeNS(KoXmlNS::text, "display-levels", QString()); const QString styleName = style.attributeNS(KoXmlNS::text, "style-name", QString()); KoCharacterStyle *cs = 0; if (!styleName.isEmpty()) { // debugText << "Should use the style =>" << styleName << "<="; KoSharedLoadingData *sharedData = scontext.sharedData(KOTEXT_SHARED_LOADING_ID); KoTextSharedLoadingData *textSharedData = 0; if (sharedData) { textSharedData = dynamic_cast(sharedData); } if (textSharedData) { cs = textSharedData->characterStyle(styleName, context.useStylesAutoStyles()); if (!cs) { warnText << "Missing KoCharacterStyle!"; } else { // debugText << "==> cs.name:" << cs->name(); // debugText << "==> cs.styleId:" << cs->styleId(); setCharacterStyleId(cs->styleId()); } } } if (style.localName() == "list-level-style-bullet") { // list with bullets // special case bullets: //debugText << QChar(0x2202) << QChar(0x25CF) << QChar(0xF0B7) << QChar(0xE00C) //<< QChar(0xE00A) << QChar(0x27A2)<< QChar(0x2794) << QChar(0x2714) << QChar(0x2d) << QChar(0x2717); //1.6: KoParagCounter::loadOasisListStyle QString bulletChar = style.attributeNS(KoXmlNS::text, "bullet-char", QString()); // debugText << "style.localName()=" << style.localName() << "level=" << level << "displayLevel=" << displayLevel << "bulletChar=" << bulletChar; if (bulletChar.isEmpty()) { // list without any visible bullets setStyle(KoListStyle::CustomCharItem); setBulletCharacter(QChar()); } else { // try to determinate the bullet we should use switch (bulletChar[0].unicode()) { case 0x2022: // bullet, a small disc -> circle setStyle(KoListStyle::Bullet); break; case 0x25CF: // black circle, large disc -> disc setStyle(KoListStyle::BlackCircle); break; case 0x25CB: //white circle, no fill setStyle(KoListStyle::CircleItem); break; case 0x25C6: // losange => rhombus setStyle(KoListStyle::RhombusItem); break; case 0x25A0: // square. Not in OASIS (reserved Unicode area!), but used in both OOo and kotext. setStyle(KoListStyle::SquareItem); break; case 0x27A2: // two-colors right-pointing triangle setStyle(KoListStyle::RightArrowHeadItem); break; case 0x2794: // arrow to right setStyle(KoListStyle::RightArrowItem); break; case 0x2714: // checkmark setStyle(KoListStyle::HeavyCheckMarkItem); break; case 0x2d: // minus setStyle(KoListStyle::CustomCharItem); break; case 0x2717: // cross setStyle(KoListStyle::BallotXItem); break; default: QChar customBulletChar = bulletChar[0]; debugText << "Unhandled bullet code 0x" << QString::number((uint)customBulletChar.unicode(), 16) << bulletChar; debugText << "Should use the style =>" << style.attributeNS(KoXmlNS::text, "style-name", QString()) << "<="; setStyle(KoListStyle::CustomCharItem); /* QString customBulletFont; // often StarSymbol when it comes from OO; doesn't matter, Qt finds it in another font if needed. if ( listStyleProperties.hasAttributeNS( KoXmlNS::style, "font-name" ) ) { customBulletFont = listStyleProperties.attributeNS( KoXmlNS::style, "font-name", QString() ); debugText <<"customBulletFont style:font-name =" << listStyleProperties.attributeNS( KoXmlNS::style,"font-name", QString() ); } else if ( listStyleTextProperties.hasAttributeNS( KoXmlNS::fo, "font-family" ) ) { customBulletFont = listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", QString() ); debugText <<"customBulletFont fo:font-family =" << listStyleTextProperties.attributeNS( KoXmlNS::fo,"font-family", QString() ); } // ## TODO in fact we're supposed to read it from the style pointed to by text:style-name */ // setStyle(KoListStyle::BoxItem); //fallback break; } // switch setBulletCharacter(bulletChar[0]); } QString size = style.attributeNS(KoXmlNS::text, "bullet-relative-size", QString()); if (!size.isEmpty()) { setRelativeBulletSize(size.remove('%').toInt()); } } else if (style.localName() == "list-level-style-number" || style.localName() == "outline-level-style") { // it's a numbered list if (style.localName() == "outline-level-style") { setOutlineList(true); } setRelativeBulletSize(100); //arbitrary value for numbered list KoOdfNumberDefinition numberDefinition; numberDefinition.loadOdf(style); switch(numberDefinition.formatSpecification()) { case KoOdfNumberDefinition::Empty: setStyle(KoListStyle::None); break; case KoOdfNumberDefinition::AlphabeticLowerCase: setStyle(KoListStyle::AlphaLowerItem); break; case KoOdfNumberDefinition::AlphabeticUpperCase: setStyle(KoListStyle::UpperAlphaItem); break; case KoOdfNumberDefinition::RomanLowerCase: setStyle(KoListStyle::RomanLowerItem); break; case KoOdfNumberDefinition::RomanUpperCase: setStyle(KoListStyle::UpperRomanItem); break; case KoOdfNumberDefinition::ArabicAlphabet: setStyle(KoListStyle::ArabicAlphabet); break; case KoOdfNumberDefinition::Thai: setStyle(KoListStyle::Thai); break; case KoOdfNumberDefinition::Abjad: setStyle(KoListStyle::Abjad); break; case KoOdfNumberDefinition::AbjadMinor: setStyle(KoListStyle::AbjadMinor); break; case KoOdfNumberDefinition::Tibetan: setStyle(KoListStyle::Tibetan); break; case KoOdfNumberDefinition::Telugu: setStyle(KoListStyle::Telugu); break; case KoOdfNumberDefinition::Tamil: setStyle(KoListStyle::Tamil); break; case KoOdfNumberDefinition::Oriya: setStyle(KoListStyle::Oriya); break; case KoOdfNumberDefinition::Malayalam: setStyle(KoListStyle::Malayalam); break; case KoOdfNumberDefinition::Kannada: setStyle(KoListStyle::Kannada); break; case KoOdfNumberDefinition::Gurumukhi: setStyle(KoListStyle::Gurumukhi); break; case KoOdfNumberDefinition::Gujarati: setStyle(KoListStyle::Gujarati); break; case KoOdfNumberDefinition::Bengali: setStyle(KoListStyle::Bengali); break; case KoOdfNumberDefinition::Numeric: default: setStyle(KoListStyle::DecimalItem); } if (!numberDefinition.prefix().isNull()) { setListItemPrefix(numberDefinition.prefix()); } if (!numberDefinition.suffix().isNull()) { setListItemSuffix(numberDefinition.suffix()); } const QString startValue = style.attributeNS(KoXmlNS::text, "start-value", QString("1")); setStartValue(startValue.toInt()); } else if (style.localName() == "list-level-style-image") { // list with image setStyle(KoListStyle::ImageItem); KoImageCollection *imageCollection = scontext.imageCollection(); const QString href = style.attribute("href"); if(imageCollection) { if (!href.isEmpty()) { KoStore *store = context.store(); setBulletImage(imageCollection->createImageData(href, store)); } else { // check if we have an office:binary data element containing the image data const KoXmlElement &binaryData(KoXml::namedItemNS(style, KoXmlNS::office, "binary-data")); if (!binaryData.isNull()) { QImage image; if (image.loadFromData(QByteArray::fromBase64(binaryData.text().toLatin1()))) { setBulletImage(imageCollection->createImageData(image)); } } } } } else { // if not defined, we have do nothing // debugText << "stylename else:" << style.localName() << "level=" << level << "displayLevel=" << displayLevel; setStyle(KoListStyle::DecimalItem); setListItemSuffix("."); } setLevel(level); if (!displayLevel.isEmpty()) setDisplayLevel(displayLevel.toInt()); KoXmlElement property; forEachElement(property, style) { if (property.namespaceURI() != KoXmlNS::style) continue; const QString localName = property.localName(); if (localName == "list-level-properties") { QString mode(property.attributeNS(KoXmlNS::text, "list-level-position-and-space-mode")); if (mode == "label-alignment") { QString textAlign(property.attributeNS(KoXmlNS::fo, "text-align")); setAlignment(textAlign.isEmpty() ? Qt::AlignLeft : KoText::alignmentFromString(textAlign)); KoXmlElement p; forEachElement(p, property) { if (p.namespaceURI() == KoXmlNS::style && p.localName() == "list-level-label-alignment") { // The element and the fo:text-align attribute are used to define // the position and spacing of the list label and the list item. The values of the attributes for // text:space-before, text:min-label-width and text:min-label-distance are assumed to be 0. setAlignmentMode(true); QString textindent(p.attributeNS(KoXmlNS::fo, "text-indent")); QString marginleft(p.attributeNS(KoXmlNS::fo, "margin-left")); qreal ti = textindent.isEmpty() ? 0 : KoUnit::parseValue(textindent); qreal ml = marginleft.isEmpty() ? 0 : KoUnit::parseValue(marginleft); setTextIndent(ti); setMargin(ml); QString labelFollowedBy(p.attributeNS(KoXmlNS::text, "label-followed-by","space")); if(labelFollowedBy.compare("listtab",Qt::CaseInsensitive)==0) { setLabelFollowedBy(KoListStyle::ListTab); // list tab position is evaluated only if label is followed by listtab // the it is only evaluated if there is a list-tab-stop-position specified // if not specified use the fo:margin-left: QString tabStop(p.attributeNS(KoXmlNS::text, "list-tab-stop-position")); if (!tabStop.isEmpty()) { qreal tabStopPos = KoUnit::parseValue(tabStop); setTabStopPosition(qMax(0.0, tabStopPos)); } }else if(labelFollowedBy.compare("nothing",Qt::CaseInsensitive)==0) { setLabelFollowedBy(KoListStyle::Nothing); }else { setLabelFollowedBy(KoListStyle::Space); } setMinimumWidth(0); setMinimumDistance(0); //TODO support ODF 18.829 text:label-followed-by and 18.832 text:list-tab-stop-position } } } if(alignmentMode()!=true ){ // default is mode == "label-width-and-position" // The text:space-before, text:min-label-width, text:minimum-label-distance and fo:text-align attributes // are used to define the position and spacing of the list label and the list item. setAlignmentMode(false); QString spaceBefore(property.attributeNS(KoXmlNS::text, "space-before")); if (!spaceBefore.isEmpty()) setIndent(KoUnit::parseValue(spaceBefore)); QString minLableWidth(property.attributeNS(KoXmlNS::text, "min-label-width")); if (!minLableWidth.isEmpty()) setMinimumWidth(KoUnit::parseValue(minLableWidth)); QString textAlign(property.attributeNS(KoXmlNS::fo, "text-align")); if (!textAlign.isEmpty()) setAlignment(KoText::alignmentFromString(textAlign)); QString minLableDistance(property.attributeNS(KoXmlNS::text, "min-label-distance")); if (!minLableDistance.isEmpty()) setMinimumDistance(KoUnit::parseValue(minLableDistance)); } QString width(property.attributeNS(KoXmlNS::fo, "width")); if (!width.isEmpty()) setWidth(KoUnit::parseValue(width)); QString height(property.attributeNS(KoXmlNS::fo, "height")); if (!height.isEmpty()) setHeight(KoUnit::parseValue(height)); } else if (localName == "text-properties") { QSharedPointer charStyle(new KoCharacterStyle); charStyle->loadOdf(&style, scontext); setCharacterProperties(charStyle); } } } void KoListLevelProperties::saveOdf(KoXmlWriter *writer, KoShapeSavingContext &context) const { bool isNumber = KoListStyle::isNumberingStyle(d->stylesPrivate.value(QTextListFormat::ListStyle).toInt()); if (isNumber || isOutlineList()) { if (isOutlineList()) { writer->startElement("text:outline-level-style"); } else { writer->startElement("text:list-level-style-number"); } if (d->stylesPrivate.contains(KoListStyle::StartValue)) writer->addAttribute("text:start-value", d->stylesPrivate.value(KoListStyle::StartValue).toInt()); if (d->stylesPrivate.contains(KoListStyle::DisplayLevel)) writer->addAttribute("text:display-levels", d->stylesPrivate.value(KoListStyle::DisplayLevel).toInt()); QByteArray format; switch (style()) { case KoListStyle::DecimalItem: format = "1"; break; case KoListStyle::AlphaLowerItem: format = "a"; break; case KoListStyle::UpperAlphaItem: format = "A"; break; case KoListStyle::RomanLowerItem: format = "i"; break; case KoListStyle::UpperRomanItem: format = "I"; break; case KoListStyle::ArabicAlphabet: format = "أ, ب, ت, ..."; break; case KoListStyle::Thai: format = "ก, ข, ค, ..."; break; case KoListStyle::Abjad: format = "أ, ب, ج, ..."; break; case KoListStyle::AbjadMinor: format = "ﺃ,ﺏ, ﺝ, ... "; break; case KoListStyle::Telugu: format = "౧, ౨, ౩, ..."; break; case KoListStyle::Tamil: format = "௧, ௨, ௪, ..."; break; case KoListStyle::Oriya: format = "୧, ୨, ୩, ..."; break; case KoListStyle::Malayalam: format = "൧, ൨, ൩, ..."; break; case KoListStyle::Kannada: format = "೧, ೨, ೩, ..."; break; case KoListStyle::Gurumukhi: format = "੧, ੨, ੩, ..."; break; case KoListStyle::Gujarati: format = "૧, ૨, ૩, ..."; break; case KoListStyle::Bengali: format = "১, ২, ৩, ..."; break; default: format = ""; break; } writer->addAttribute("style:num-format", format); } else if (style() == KoListStyle::ImageItem) { KoImageData *imageData = d->stylesPrivate.value(KoListStyle::BulletImage).value(); if (imageData && imageData->priv()->collection) { writer->startElement("text:list-level-style-image"); writer->addAttribute("xlink:show", "embed"); writer->addAttribute("xlink:actuate", "onLoad"); writer->addAttribute("xlink:type", "simple"); writer->addAttribute("xlink:href", context.imageHref(imageData)); context.addDataCenter(imageData->priv()->collection); } } else { writer->startElement("text:list-level-style-bullet"); int bullet; if (d->stylesPrivate.contains(KoListStyle::BulletCharacter)) { bullet = d->stylesPrivate.value(KoListStyle::BulletCharacter).toInt(); } else { // try to determine the bullet character from the style bullet = KoListStyle::bulletCharacter(style()); } writer->addAttribute("text:bullet-char", QChar(bullet)); } KoTextSharedSavingData *sharedSavingData = 0; if (d->stylesPrivate.contains(KoListStyle::CharacterStyleId) && (characterStyleId() != 0) && (sharedSavingData = static_cast(context.sharedData(KOTEXT_SHARED_SAVING_ID)))) { QString styleName = sharedSavingData->styleName(characterStyleId()); // dynamic_cast(context.sharedData(KOTEXT_SHARED_SAVING_ID))->styleName(characterStyleId()); if (!styleName.isEmpty()) { writer->addAttribute("text:style-name", styleName); } } // These apply to bulleted and numbered lists if (d->stylesPrivate.contains(KoListStyle::Level)) writer->addAttribute("text:level", d->stylesPrivate.value(KoListStyle::Level).toInt()); if (d->stylesPrivate.contains(KoListStyle::ListItemPrefix)) writer->addAttribute("style:num-prefix", d->stylesPrivate.value(KoListStyle::ListItemPrefix).toString()); if (d->stylesPrivate.contains(KoListStyle::ListItemSuffix)) writer->addAttribute("style:num-suffix", d->stylesPrivate.value(KoListStyle::ListItemSuffix).toString()); writer->startElement("style:list-level-properties", false); if (d->stylesPrivate.contains(KoListStyle::Width)) { - writer->addAttributePt("fo:width", width()); + writer->addAttribute("fo:width", width()); } if (d->stylesPrivate.contains(KoListStyle::Height)) { - writer->addAttributePt("fo:height", height()); + writer->addAttribute("fo:height", height()); } if(d->stylesPrivate.contains(KoListStyle::AlignmentMode) && alignmentMode()==false) { writer->addAttribute("text:list-level-position-and-space-mode","label-width-and-position"); if (d->stylesPrivate.contains(KoListStyle::Indent)) - writer->addAttributePt("text:space-before", indent()); + writer->addAttribute("text:space-before", indent()); if (d->stylesPrivate.contains(KoListStyle::MinimumWidth)) - writer->addAttributePt("text:min-label-width", minimumWidth()); + writer->addAttribute("text:min-label-width", minimumWidth()); if (d->stylesPrivate.contains(KoListStyle::Alignment)) writer->addAttribute("fo:text-align", KoText::alignmentToString(alignment())); if (d->stylesPrivate.contains(KoListStyle::MinimumDistance)) - writer->addAttributePt("text:min-label-distance", minimumDistance()); + writer->addAttribute("text:min-label-distance", minimumDistance()); } else { writer->addAttribute("text:list-level-position-and-space-mode","label-alignment"); if (d->stylesPrivate.contains(KoListStyle::Alignment)) writer->addAttribute("fo:text-align", KoText::alignmentToString(alignment())); writer->startElement("style:list-level-label-alignment"); if(labelFollowedBy()==KoListStyle::ListTab) { writer->addAttribute("text:label-followed-by","listtab"); - writer->addAttributePt("text:list-tab-stop-position", tabStopPosition()); + writer->addAttribute("text:list-tab-stop-position", tabStopPosition()); } else if (labelFollowedBy()==KoListStyle::Nothing){ writer->addAttribute("text:label-followed-by","nothing"); }else{ writer->addAttribute("text:label-followed-by","space"); } - writer->addAttributePt("fo:text-indent", textIndent()); - writer->addAttributePt("fo:margin-left", margin()); + writer->addAttribute("fo:text-indent", textIndent()); + writer->addAttribute("fo:margin-left", margin()); writer->endElement(); } writer->endElement(); // list-level-properties // text properties if (d->stylesPrivate.contains(KoListStyle::CharacterProperties)) { KoGenStyle liststyle(KoGenStyle::ListStyle); QSharedPointer cs = characterProperties(); cs->saveOdf(liststyle); liststyle.writeStyleProperties(writer, KoGenStyle::TextType); } // debugText << "Key KoListStyle::ListItemPrefix :" << d->stylesPrivate.value(KoListStyle::ListItemPrefix); // debugText << "Key KoListStyle::ListItemSuffix :" << d->stylesPrivate.value(KoListStyle::ListItemSuffix); // debugText << "Key KoListStyle::CharacterStyleId :" << d->stylesPrivate.value(KoListStyle::CharacterStyleId); // debugText << "Key KoListStyle::RelativeBulletSize :" << d->stylesPrivate.value(KoListStyle::RelativeBulletSize); // debugText << "Key KoListStyle::Alignment :" << d->stylesPrivate.value(KoListStyle::Alignment); // debugText << "Key KoListStyle::LetterSynchronization :" << d->stylesPrivate.value(KoListStyle::LetterSynchronization); writer->endElement(); } diff --git a/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp b/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp index 776d42a616..bede0385b6 100644 --- a/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp @@ -1,2367 +1,2367 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2007,2008 Sebastian Sauer * Copyright (C) 2007-2011 Pierre Ducroquet * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Gopalakrishna Bhat A * * 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 "KoParagraphStyle.h" #include "KoList.h" #include "KoListStyle.h" #include "KoTextBlockData.h" #include "KoStyleManager.h" #include "KoListLevelProperties.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include "Styles_p.h" #include "KoTextDocument.h" #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include //already defined in KoRulerController.cpp #ifndef KDE_USE_FINAL struct { bool operator()(KoText::Tab tab1, KoText::Tab tab2) const { return tab1.position < tab2.position; } } compareTabs; #endif class Q_DECL_HIDDEN KoParagraphStyle::Private { public: Private() : parentStyle(0), defaultStyle(0), list(0), m_inUse(false) {} ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } void ensureDefaults(QTextBlockFormat &format) { if (defaultStyle) { QMap props = defaultStyle->d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } } QString name; KoParagraphStyle *parentStyle; KoParagraphStyle *defaultStyle; KoList *list; StylePrivate stylesPrivate; bool m_inUse; }; KoParagraphStyle::KoParagraphStyle(QObject *parent) : KoCharacterStyle(parent), d(new Private()) { } KoParagraphStyle::KoParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &blockCharFormat, QObject *parent) : KoCharacterStyle(blockCharFormat, parent), d(new Private()) { d->stylesPrivate = blockFormat.properties(); } KoParagraphStyle *KoParagraphStyle::fromBlock(const QTextBlock &block, QObject *parent) { QTextBlockFormat blockFormat = block.blockFormat(); QTextCursor cursor(block); KoParagraphStyle *answer = new KoParagraphStyle(blockFormat, cursor.blockCharFormat(), parent); int listStyleId = blockFormat.intProperty(ListStyleId); KoStyleManager *sm = KoTextDocument(block.document()).styleManager(); if (KoListStyle *listStyle = sm->listStyle(listStyleId)) { answer->setListStyle(listStyle->clone(answer)); } else if (block.textList()) { KoListLevelProperties llp = KoListLevelProperties::fromTextList(block.textList()); KoListStyle *listStyle = new KoListStyle(answer); listStyle->setLevelProperties(llp); answer->setListStyle(listStyle); } return answer; } KoParagraphStyle::~KoParagraphStyle() { delete d; } KoCharacterStyle::Type KoParagraphStyle::styleType() const { return KoCharacterStyle::ParagraphStyle; } void KoParagraphStyle::setDefaultStyle(KoParagraphStyle *defaultStyle) { d->defaultStyle = defaultStyle; KoCharacterStyle::setDefaultStyle(defaultStyle); } void KoParagraphStyle::setParentStyle(KoParagraphStyle *parent) { d->parentStyle = parent; KoCharacterStyle::setParentStyle(parent); } void KoParagraphStyle::setProperty(int key, const QVariant &value) { if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoParagraphStyle::remove(int key) { d->stylesPrivate.remove(key); } QVariant KoParagraphStyle::value(int key) const { QVariant var = d->stylesPrivate.value(key); if (var.isNull()) { if (d->parentStyle) return d->parentStyle->value(key); else if (d->defaultStyle) return d->defaultStyle->value(key); } return var; } bool KoParagraphStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } qreal KoParagraphStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QTextLength KoParagraphStyle::propertyLength(int key) const { QVariant variant = value(key); if (variant.isNull()) return QTextLength(QTextLength::FixedLength, 0.0); if (!variant.canConvert()) { // Fake support, for compatibility sake if (variant.canConvert()) { return QTextLength(QTextLength::FixedLength, variant.toReal()); } warnText << "This should never happen : requested property can't be converted to QTextLength"; return QTextLength(QTextLength::FixedLength, 0.0); } return variant.value(); } int KoParagraphStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoParagraphStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoParagraphStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoParagraphStyle::applyStyle(QTextBlockFormat &format) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } const QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { if (it.key() == QTextBlockFormat::BlockLeftMargin) { format.setLeftMargin(leftMargin()); } else if (it.key() == QTextBlockFormat::BlockRightMargin) { format.setRightMargin(rightMargin()); } else if (it.key() == QTextBlockFormat::TextIndent) { format.setTextIndent(textIndent()); } else { format.setProperty(it.key(), it.value()); } ++it; } if ((hasProperty(DefaultOutlineLevel)) && (!format.hasProperty(OutlineLevel))) { format.setProperty(OutlineLevel, defaultOutlineLevel()); } emit styleApplied(this); d->m_inUse = true; } void KoParagraphStyle::applyStyle(QTextBlock &block, bool applyListStyle) const { QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); applyStyle(format); d->ensureDefaults(format); cursor.setBlockFormat(format); KoCharacterStyle::applyStyle(block); if (applyListStyle) { applyParagraphListStyle(block, format); } } bool KoParagraphStyle::isApplied() const { return d->m_inUse; } void KoParagraphStyle::applyParagraphListStyle(QTextBlock &block, const QTextBlockFormat &blockFormat) const { //gopalakbhat: We need to differentiate between normal styles and styles with outline(part of heading) //Styles part of outline: We ignore the listStyle()( even if this is a valid in ODF; even LibreOffice does the same) // since we can specify all info required in text:outline-style //Normal styles: we use the listStyle() if (blockFormat.hasProperty(OutlineLevel)) { if (! d->list) { if (! KoTextDocument(block.document()).headingList()) { if (KoTextDocument(block.document()).styleManager() && KoTextDocument(block.document()).styleManager()->outlineStyle()) { d->list = new KoList(block.document(), KoTextDocument(block.document()).styleManager()->outlineStyle()); KoTextDocument(block.document()).setHeadingList(d->list); } } else { d->list = KoTextDocument(block.document()).headingList(); } } if (d->list) { d->list->applyStyle(block, KoTextDocument(block.document()).styleManager()->outlineStyle(), blockFormat.intProperty(OutlineLevel)); } } else { if (listStyle()) { if (!d->list) { d->list = new KoList(block.document(), listStyle()); } //FIXME: Gopalakrishna Bhat A: This condition should never happen. // i.e. d->list->style() should always be in sync with the listStyle() if (d->list->style() != listStyle()) { d->list->setStyle(listStyle()); } d->list->add(block, listLevel()); } else { if (block.textList()) block.textList()->remove(block); KoTextBlockData data(block); data.setCounterWidth(-1); } } } void KoParagraphStyle::unapplyStyle(QTextBlock &block) const { if (d->parentStyle) d->parentStyle->unapplyStyle(block); QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); if (keys[i] == QTextBlockFormat::BlockLeftMargin) { if (leftMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::BlockRightMargin) { if (rightMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::TextIndent) { if (textIndent() == format.property(keys[i])) format.clearProperty(keys[i]); } else { if (variant == format.property(keys[i])) format.clearProperty(keys[i]); } } format.clearProperty(KoParagraphStyle::OutlineLevel); cursor.setBlockFormat(format); KoCharacterStyle::unapplyStyle(block); if (listStyle() && block.textList()) { // TODO check its the same one? KoList::remove(block); } if (d->list && block.textList()) { // TODO check its the same one? KoList::remove(block); } } void KoParagraphStyle::setLineHeightPercent(qreal lineHeight) { setProperty(PercentLineHeight, lineHeight); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightPercent() const { return propertyInt(PercentLineHeight); } void KoParagraphStyle::setLineHeightAbsolute(qreal height) { setProperty(FixedLineHeight, height); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightAbsolute() const { return propertyDouble(FixedLineHeight); } void KoParagraphStyle::setMinimumLineHeight(const QTextLength &height) { setProperty(FixedLineHeight, 0.0); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, height); remove(NormalLineHeight); } qreal KoParagraphStyle::minimumLineHeight() const { if (parentStyle()) return propertyLength(MinimumLineHeight).value(parentStyle()->minimumLineHeight()); else return propertyLength(MinimumLineHeight).value(0); } void KoParagraphStyle::setLineSpacing(qreal spacing) { setProperty(LineSpacing, spacing); remove(NormalLineHeight); } qreal KoParagraphStyle::lineSpacing() const { return propertyDouble(LineSpacing); } void KoParagraphStyle::setLineSpacingFromFont(bool on) { setProperty(LineSpacingFromFont, on); remove(NormalLineHeight); } bool KoParagraphStyle::lineSpacingFromFont() const { return propertyBoolean(LineSpacingFromFont); } void KoParagraphStyle::setNormalLineHeight() { setProperty(NormalLineHeight, true); setProperty(PercentLineHeight, 0); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); setProperty(LineSpacing, 0.0); } bool KoParagraphStyle::hasNormalLineHeight() const { return propertyBoolean(NormalLineHeight); } void KoParagraphStyle::setAlignLastLine(Qt::Alignment alignment) { setProperty(AlignLastLine, (int) alignment); } Qt::Alignment KoParagraphStyle::alignLastLine() const { if (hasProperty(AlignLastLine)) return static_cast(propertyInt(AlignLastLine)); // Hum, that doesn't sound right ! return alignment(); } void KoParagraphStyle::setWidowThreshold(int lines) { setProperty(WidowThreshold, lines); } int KoParagraphStyle::widowThreshold() const { return propertyInt(WidowThreshold); } void KoParagraphStyle::setOrphanThreshold(int lines) { setProperty(OrphanThreshold, lines); } int KoParagraphStyle::orphanThreshold() const { return propertyInt(OrphanThreshold); } void KoParagraphStyle::setDropCaps(bool on) { setProperty(DropCaps, on); } bool KoParagraphStyle::dropCaps() const { return propertyBoolean(DropCaps); } void KoParagraphStyle::setDropCapsLength(int characters) { setProperty(DropCapsLength, characters); } int KoParagraphStyle::dropCapsLength() const { return propertyInt(DropCapsLength); } void KoParagraphStyle::setDropCapsLines(int lines) { setProperty(DropCapsLines, lines); } int KoParagraphStyle::dropCapsLines() const { return propertyInt(DropCapsLines); } void KoParagraphStyle::setDropCapsDistance(qreal distance) { setProperty(DropCapsDistance, distance); } qreal KoParagraphStyle::dropCapsDistance() const { return propertyDouble(DropCapsDistance); } void KoParagraphStyle::setDropCapsTextStyleId(int id) { setProperty(KoParagraphStyle::DropCapsTextStyle, id); } int KoParagraphStyle::dropCapsTextStyleId() const { return propertyInt(KoParagraphStyle::DropCapsTextStyle); } void KoParagraphStyle::setFollowDocBaseline(bool on) { setProperty(FollowDocBaseline, on); } bool KoParagraphStyle::followDocBaseline() const { return propertyBoolean(FollowDocBaseline); } void KoParagraphStyle::setBreakBefore(KoText::KoTextBreakProperty value) { setProperty(BreakBefore, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakBefore() const { return static_cast(propertyInt(BreakBefore)); } void KoParagraphStyle::setBreakAfter(KoText::KoTextBreakProperty value) { setProperty(BreakAfter, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakAfter() const { return static_cast(propertyInt(BreakAfter)); } void KoParagraphStyle::setLeftPadding(qreal padding) { setProperty(LeftPadding, padding); } qreal KoParagraphStyle::leftPadding() const { return propertyDouble(LeftPadding); } void KoParagraphStyle::setTopPadding(qreal padding) { setProperty(TopPadding, padding); } qreal KoParagraphStyle::topPadding() const { return propertyDouble(TopPadding); } void KoParagraphStyle::setRightPadding(qreal padding) { setProperty(RightPadding, padding); } qreal KoParagraphStyle::rightPadding() const { return propertyDouble(RightPadding); } void KoParagraphStyle::setBottomPadding(qreal padding) { setProperty(BottomPadding, padding); } qreal KoParagraphStyle::bottomPadding() const { return propertyDouble(BottomPadding); } void KoParagraphStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } void KoParagraphStyle::setLeftBorderWidth(qreal width) { setProperty(LeftBorderWidth, width); } qreal KoParagraphStyle::leftBorderWidth() const { return propertyDouble(LeftBorderWidth); } void KoParagraphStyle::setLeftInnerBorderWidth(qreal width) { setProperty(LeftInnerBorderWidth, width); } qreal KoParagraphStyle::leftInnerBorderWidth() const { return propertyDouble(LeftInnerBorderWidth); } void KoParagraphStyle::setLeftBorderSpacing(qreal width) { setProperty(LeftBorderSpacing, width); } qreal KoParagraphStyle::leftBorderSpacing() const { return propertyDouble(LeftBorderSpacing); } void KoParagraphStyle::setLeftBorderStyle(KoBorder::BorderStyle style) { setProperty(LeftBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::leftBorderStyle() const { return static_cast(propertyInt(LeftBorderStyle)); } void KoParagraphStyle::setLeftBorderColor(const QColor &color) { setProperty(LeftBorderColor, color); } QColor KoParagraphStyle::leftBorderColor() const { return propertyColor(LeftBorderColor); } void KoParagraphStyle::setTopBorderWidth(qreal width) { setProperty(TopBorderWidth, width); } qreal KoParagraphStyle::topBorderWidth() const { return propertyDouble(TopBorderWidth); } void KoParagraphStyle::setTopInnerBorderWidth(qreal width) { setProperty(TopInnerBorderWidth, width); } qreal KoParagraphStyle::topInnerBorderWidth() const { return propertyDouble(TopInnerBorderWidth); } void KoParagraphStyle::setTopBorderSpacing(qreal width) { setProperty(TopBorderSpacing, width); } qreal KoParagraphStyle::topBorderSpacing() const { return propertyDouble(TopBorderSpacing); } void KoParagraphStyle::setTopBorderStyle(KoBorder::BorderStyle style) { setProperty(TopBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::topBorderStyle() const { return static_cast(propertyInt(TopBorderStyle)); } void KoParagraphStyle::setTopBorderColor(const QColor &color) { setProperty(TopBorderColor, color); } QColor KoParagraphStyle::topBorderColor() const { return propertyColor(TopBorderColor); } void KoParagraphStyle::setRightBorderWidth(qreal width) { setProperty(RightBorderWidth, width); } qreal KoParagraphStyle::rightBorderWidth() const { return propertyDouble(RightBorderWidth); } void KoParagraphStyle::setRightInnerBorderWidth(qreal width) { setProperty(RightInnerBorderWidth, width); } qreal KoParagraphStyle::rightInnerBorderWidth() const { return propertyDouble(RightInnerBorderWidth); } void KoParagraphStyle::setRightBorderSpacing(qreal width) { setProperty(RightBorderSpacing, width); } qreal KoParagraphStyle::rightBorderSpacing() const { return propertyDouble(RightBorderSpacing); } void KoParagraphStyle::setRightBorderStyle(KoBorder::BorderStyle style) { setProperty(RightBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::rightBorderStyle() const { return static_cast(propertyInt(RightBorderStyle)); } void KoParagraphStyle::setRightBorderColor(const QColor &color) { setProperty(RightBorderColor, color); } QColor KoParagraphStyle::rightBorderColor() const { return propertyColor(RightBorderColor); } void KoParagraphStyle::setBottomBorderWidth(qreal width) { setProperty(BottomBorderWidth, width); } qreal KoParagraphStyle::bottomBorderWidth() const { return propertyDouble(BottomBorderWidth); } void KoParagraphStyle::setBottomInnerBorderWidth(qreal width) { setProperty(BottomInnerBorderWidth, width); } qreal KoParagraphStyle::bottomInnerBorderWidth() const { return propertyDouble(BottomInnerBorderWidth); } void KoParagraphStyle::setBottomBorderSpacing(qreal width) { setProperty(BottomBorderSpacing, width); } qreal KoParagraphStyle::bottomBorderSpacing() const { return propertyDouble(BottomBorderSpacing); } void KoParagraphStyle::setBottomBorderStyle(KoBorder::BorderStyle style) { setProperty(BottomBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::bottomBorderStyle() const { return static_cast(propertyInt(BottomBorderStyle)); } void KoParagraphStyle::setBottomBorderColor(const QColor &color) { setProperty(BottomBorderColor, color); } QColor KoParagraphStyle::bottomBorderColor() const { return propertyColor(BottomBorderColor); } void KoParagraphStyle::setTopMargin(QTextLength topMargin) { setProperty(QTextFormat::BlockTopMargin, topMargin); } qreal KoParagraphStyle::topMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockTopMargin).value(parentStyle()->topMargin()); else return propertyLength(QTextFormat::BlockTopMargin).value(0); } void KoParagraphStyle::setBottomMargin(QTextLength margin) { setProperty(QTextFormat::BlockBottomMargin, margin); } qreal KoParagraphStyle::bottomMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockBottomMargin).value(parentStyle()->bottomMargin()); else return propertyLength(QTextFormat::BlockBottomMargin).value(0); } void KoParagraphStyle::setLeftMargin(QTextLength margin) { setProperty(QTextFormat::BlockLeftMargin, margin); } qreal KoParagraphStyle::leftMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockLeftMargin).value(parentStyle()->leftMargin()); else return propertyLength(QTextFormat::BlockLeftMargin).value(0); } void KoParagraphStyle::setRightMargin(QTextLength margin) { setProperty(QTextFormat::BlockRightMargin, margin); } qreal KoParagraphStyle::rightMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockRightMargin).value(parentStyle()->rightMargin()); else return propertyLength(QTextFormat::BlockRightMargin).value(0); } void KoParagraphStyle::setMargin(QTextLength margin) { setTopMargin(margin); setBottomMargin(margin); setLeftMargin(margin); setRightMargin(margin); } void KoParagraphStyle::setAlignment(Qt::Alignment alignment) { setProperty(QTextFormat::BlockAlignment, (int) alignment); } Qt::Alignment KoParagraphStyle::alignment() const { return static_cast(propertyInt(QTextFormat::BlockAlignment)); } void KoParagraphStyle::setTextIndent(QTextLength margin) { setProperty(QTextFormat::TextIndent, margin); } qreal KoParagraphStyle::textIndent() const { if (parentStyle()) return propertyLength(QTextFormat::TextIndent).value(parentStyle()->textIndent()); else return propertyLength(QTextFormat::TextIndent).value(0); } void KoParagraphStyle::setAutoTextIndent(bool on) { setProperty(KoParagraphStyle::AutoTextIndent, on); } bool KoParagraphStyle::autoTextIndent() const { return propertyBoolean(KoParagraphStyle::AutoTextIndent); } void KoParagraphStyle::setNonBreakableLines(bool on) { setProperty(QTextFormat::BlockNonBreakableLines, on); } bool KoParagraphStyle::nonBreakableLines() const { return propertyBoolean(QTextFormat::BlockNonBreakableLines); } void KoParagraphStyle::setKeepWithNext(bool value) { setProperty(KeepWithNext, value); } bool KoParagraphStyle::keepWithNext() const { if (hasProperty(KeepWithNext)) return propertyBoolean(KeepWithNext); return false; } bool KoParagraphStyle::punctuationWrap() const { if (hasProperty(PunctuationWrap)) return propertyBoolean(PunctuationWrap); return false; } void KoParagraphStyle::setPunctuationWrap(bool value) { setProperty(PunctuationWrap, value); } KoParagraphStyle *KoParagraphStyle::parentStyle() const { return d->parentStyle; } void KoParagraphStyle::setNextStyle(int next) { setProperty(NextStyle, next); } int KoParagraphStyle::nextStyle() const { return propertyInt(NextStyle); } QString KoParagraphStyle::name() const { return d->name; } void KoParagraphStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; KoCharacterStyle::setName(name); emit nameChanged(name); } int KoParagraphStyle::styleId() const { // duplicate some code to avoid getting the parents style id QVariant variant = d->stylesPrivate.value(StyleId); if (variant.isNull()) return 0; return variant.toInt(); } void KoParagraphStyle::setStyleId(int id) { setProperty(StyleId, id); if (nextStyle() == 0) setNextStyle(id); KoCharacterStyle::setStyleId(id); } QString KoParagraphStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoParagraphStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoParagraphStyle::setListStartValue(int value) { setProperty(ListStartValue, value); } int KoParagraphStyle::listStartValue() const { return propertyInt(ListStartValue); } void KoParagraphStyle::setRestartListNumbering(bool on) { setProperty(RestartListNumbering, on); } bool KoParagraphStyle::restartListNumbering() { return propertyBoolean(RestartListNumbering); } void KoParagraphStyle::setListLevel(int value) { setProperty(ListLevel, value); } int KoParagraphStyle::listLevel() const { return propertyInt(ListLevel); } void KoParagraphStyle::setOutlineLevel(int outline) { setProperty(OutlineLevel, outline); } int KoParagraphStyle::outlineLevel() const { return propertyInt(OutlineLevel); } void KoParagraphStyle::setDefaultOutlineLevel(int outline) { setProperty(DefaultOutlineLevel, outline); } int KoParagraphStyle::defaultOutlineLevel() const { return propertyInt(DefaultOutlineLevel); } bool KoParagraphStyle::lineNumbering() const { return propertyBoolean(LineNumbering); } void KoParagraphStyle::setLineNumbering(bool lineNumbering) { setProperty(LineNumbering, lineNumbering); } int KoParagraphStyle::lineNumberStartValue() const { return propertyInt(LineNumberStartValue); } void KoParagraphStyle::setLineNumberStartValue(int lineNumberStartValue) { setProperty(LineNumberStartValue, lineNumberStartValue); } void KoParagraphStyle::setIsListHeader(bool on) { setProperty(IsListHeader, on); } bool KoParagraphStyle::isListHeader() const { return propertyBoolean(IsListHeader); } KoListStyle *KoParagraphStyle::listStyle() const { QVariant variant = value(ParagraphListStyleId); if (variant.isNull()) return 0; return variant.value(); } void KoParagraphStyle::setListStyle(KoListStyle *style) { if (listStyle() == style) return; if (listStyle() && listStyle()->parent() == this) delete listStyle(); QVariant variant; KoListStyle *cloneStyle = 0; if (style) { cloneStyle = style->clone(); variant.setValue(cloneStyle); setProperty(ParagraphListStyleId, variant); } else { d->stylesPrivate.remove(ParagraphListStyleId); } } KoText::Direction KoParagraphStyle::textProgressionDirection() const { return static_cast(propertyInt(TextProgressionDirection)); } void KoParagraphStyle::setTextProgressionDirection(KoText::Direction dir) { setProperty(TextProgressionDirection, dir); } bool KoParagraphStyle::keepHyphenation() const { if (hasProperty(KeepHyphenation)) return propertyBoolean(KeepHyphenation); return false; } void KoParagraphStyle::setKeepHyphenation(bool value) { setProperty(KeepHyphenation, value); } int KoParagraphStyle::hyphenationLadderCount() const { if (hasProperty(HyphenationLadderCount)) return propertyInt(HyphenationLadderCount); return 0; } void KoParagraphStyle::setHyphenationLadderCount(int value) { setProperty(HyphenationLadderCount, value); } void KoParagraphStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoParagraphStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoParagraphStyle::background() const { QVariant variant = d->stylesPrivate.value(QTextFormat::BackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } qreal KoParagraphStyle::backgroundTransparency() const { if (hasProperty(BackgroundTransparency)) return propertyDouble(BackgroundTransparency); return 0.0; } void KoParagraphStyle::setBackgroundTransparency(qreal transparency) { setProperty(BackgroundTransparency, transparency); } void KoParagraphStyle::setSnapToLayoutGrid(bool value) { setProperty(SnapToLayoutGrid, value); } bool KoParagraphStyle::snapToLayoutGrid() const { if (hasProperty(SnapToLayoutGrid)) return propertyBoolean(SnapToLayoutGrid); return false; } bool KoParagraphStyle::joinBorder() const { if (hasProperty(JoinBorder)) return propertyBoolean(JoinBorder); return true; //default is true } void KoParagraphStyle::setJoinBorder(bool value) { setProperty(JoinBorder, value); } int KoParagraphStyle::pageNumber() const { return propertyInt(PageNumber); } void KoParagraphStyle::setPageNumber(int pageNumber) { if (pageNumber >= 0) setProperty(PageNumber, pageNumber); } bool KoParagraphStyle::automaticWritingMode() const { if (hasProperty(AutomaticWritingMode)) return propertyBoolean(AutomaticWritingMode); return true; } void KoParagraphStyle::setAutomaticWritingMode(bool value) { setProperty(AutomaticWritingMode, value); } void KoParagraphStyle::setVerticalAlignment(KoParagraphStyle::VerticalAlign value) { setProperty(VerticalAlignment, value); } KoParagraphStyle::VerticalAlign KoParagraphStyle::verticalAlignment() const { if (hasProperty(VerticalAlignment)) return (VerticalAlign) propertyInt(VerticalAlignment); return VAlignAuto; } void KoParagraphStyle::setShadow(const KoShadowStyle &shadow) { d->setProperty(Shadow, QVariant::fromValue(shadow)); } KoShadowStyle KoParagraphStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoParagraphStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext, bool loadParents) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString())); if (!name.isEmpty()) { setName(name); } else { setName(element->attributeNS(KoXmlNS::style, "name", QString())); } QString family = element->attributeNS(KoXmlNS::style, "family", "paragraph"); context.styleStack().save(); if (loadParents) { context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent } else { context.styleStack().push(*element); } context.styleStack().setTypeProperties("text"); // load the style:text-properties KoCharacterStyle::loadOdfProperties(scontext); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } if (element->hasAttributeNS(KoXmlNS::style, "default-outline-level")) { bool ok = false; int level = element->attributeNS(KoXmlNS::style, "default-outline-level").toInt(&ok); if (ok) setDefaultOutlineLevel(level); } context.styleStack().setTypeProperties("paragraph"); // load all style attributes from "style:paragraph-properties" loadOdfProperties(scontext); // load the KoParagraphStyle from the stylestack context.styleStack().restore(); } struct ParagraphBorderData { enum Values {Style = 1, Color = 2, Width = 4}; ParagraphBorderData() : values(0) {} ParagraphBorderData(const ParagraphBorderData &other) : values(other.values), style(other.style), color(other.color), width(other.width) {} // flag defining which data is set int values; KoBorder::BorderStyle style; QColor color; qreal width; ///< in pt }; /// Parses the @p dataString as value defined by CSS2 §7.29.3 "border" /// Adds parsed data to the data as set for @p defaultParagraphBorderData. /// Returns the enriched border data on success, the original @p defaultParagraphBorderData on a parsing error static ParagraphBorderData parseParagraphBorderData(const QString &dataString, const ParagraphBorderData &defaultParagraphBorderData) { const QStringList bv = dataString.split(QLatin1Char(' '), QString::SkipEmptyParts); // too many items? ignore complete value if (bv.count() > 3) { return defaultParagraphBorderData; } ParagraphBorderData borderData = defaultParagraphBorderData; int parsedValues = 0; ///< used to track what is read from the given string Q_FOREACH (const QString &v, bv) { // try style if (! (parsedValues & ParagraphBorderData::Style)) { bool success = false; KoBorder::BorderStyle style = KoBorder::odfBorderStyle(v, &success); // workaround for not yet supported "hidden" if (! success && (v == QLatin1String("hidden"))) { // map to "none" for now TODO: KoBorder needs to support "hidden" style = KoBorder::BorderNone; success = true; } if (success) { borderData.style = style; borderData.values |= ParagraphBorderData::Style; parsedValues |= ParagraphBorderData::Style; continue; } } // try color if (! (parsedValues & ParagraphBorderData::Color)) { const QColor color(v); if (color.isValid()) { borderData.color = color; borderData.values |= ParagraphBorderData::Color; parsedValues |= ParagraphBorderData::Color; continue; } } // try width if (! (parsedValues & ParagraphBorderData::Width)) { const qreal width = KoUnit::parseValue(v); if (width >= 0.0) { borderData.width = width; borderData.values |= ParagraphBorderData::Width; parsedValues |= ParagraphBorderData::Width; continue; } } // still here? found a value which cannot be parsed return defaultParagraphBorderData; } return borderData; } void KoParagraphStyle::loadOdfProperties(KoShapeLoadingContext &scontext) { KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack(); // in 1.6 this was defined at KoParagLayout::loadOasisParagLayout(KoParagLayout&, KoOasisContext&) const QString writingMode(styleStack.property(KoXmlNS::style, "writing-mode")); if (!writingMode.isEmpty()) { setTextProgressionDirection(KoText::directionFromString(writingMode)); } // Alignment const QString textAlign(styleStack.property(KoXmlNS::fo, "text-align")); if (!textAlign.isEmpty()) { setAlignment(KoText::alignmentFromString(textAlign)); } // Spacing (padding) const QString padding(styleStack.property(KoXmlNS::fo, "padding")); if (!padding.isEmpty()) { setPadding(KoUnit::parseValue(padding)); } const QString paddingLeft(styleStack.property(KoXmlNS::fo, "padding-left" )); if (!paddingLeft.isEmpty()) { setLeftPadding(KoUnit::parseValue(paddingLeft)); } const QString paddingRight(styleStack.property(KoXmlNS::fo, "padding-right" )); if (!paddingRight.isEmpty()) { setRightPadding(KoUnit::parseValue(paddingRight)); } const QString paddingTop(styleStack.property(KoXmlNS::fo, "padding-top" )); if (!paddingTop.isEmpty()) { setTopPadding(KoUnit::parseValue(paddingTop)); } const QString paddingBottom(styleStack.property(KoXmlNS::fo, "padding-bottom" )); if (!paddingBottom.isEmpty()) { setBottomPadding(KoUnit::parseValue(paddingBottom)); } // Indentation (margin) const QString margin(styleStack.property(KoXmlNS::fo, "margin")); if (!margin.isEmpty()) { setMargin(KoText::parseLength(margin)); } const QString marginLeft(styleStack.property(KoXmlNS::fo, "margin-left" )); if (!marginLeft.isEmpty()) { setLeftMargin(KoText::parseLength(marginLeft)); } const QString marginRight(styleStack.property(KoXmlNS::fo, "margin-right" )); if (!marginRight.isEmpty()) { setRightMargin(KoText::parseLength(marginRight)); } const QString marginTop(styleStack.property(KoXmlNS::fo, "margin-top")); if (!marginTop.isEmpty()) { setTopMargin(KoText::parseLength(marginTop)); } const QString marginBottom(styleStack.property(KoXmlNS::fo, "margin-bottom")); if (!marginBottom.isEmpty()) { setBottomMargin(KoText::parseLength(marginBottom)); } // Automatic Text indent // OOo is not assuming this. Commenting this line thus allow more OpenDocuments to be supported, including a // testcase from the ODF test suite. See §15.5.18 in the spec. //if ( hasMarginLeft || hasMarginRight ) { // style:auto-text-indent takes precedence const QString autoTextIndent(styleStack.property(KoXmlNS::style, "auto-text-indent")); if (!autoTextIndent.isEmpty()) { setAutoTextIndent(autoTextIndent == "true"); } if (autoTextIndent != "true" || autoTextIndent.isEmpty()) { const QString textIndent(styleStack.property(KoXmlNS::fo, "text-indent")); if (!textIndent.isEmpty()) { setTextIndent(KoText::parseLength(textIndent)); } } //} // Line spacing QString lineHeight(styleStack.property(KoXmlNS::fo, "line-height")); if (!lineHeight.isEmpty()) { if (lineHeight != "normal") { if (lineHeight.indexOf('%') > -1) { bool ok; const qreal percent = lineHeight.remove('%').toDouble(&ok); if (ok) { setLineHeightPercent(percent); } } else { // fixed value is between 0.0201in and 3.9402in const qreal value = KoUnit::parseValue(lineHeight, -1.0); if (value >= 0.0) { setLineHeightAbsolute(value); } } } else { setNormalLineHeight(); } } else { const QString lineSpacing(styleStack.property(KoXmlNS::style, "line-spacing")); if (!lineSpacing.isEmpty()) { // 3.11.3 setLineSpacing(KoUnit::parseValue(lineSpacing)); } } // 15.5.30 - 31 if (styleStack.hasProperty(KoXmlNS::text, "number-lines")) { setLineNumbering(styleStack.property(KoXmlNS::text, "number-lines", "false") == "true"); } if (styleStack.hasProperty(KoXmlNS::text, "line-number")) { bool ok; int startValue = styleStack.property(KoXmlNS::text, "line-number").toInt(&ok); if (ok) { setLineNumberStartValue(startValue); } } const QString lineHeightAtLeast(styleStack.property(KoXmlNS::style, "line-height-at-least")); if (!lineHeightAtLeast.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { // 3.11.2 setMinimumLineHeight(KoText::parseLength(lineHeightAtLeast)); } // Line-height-at-least is mutually exclusive with absolute line-height const QString fontIndependentLineSpacing(styleStack.property(KoXmlNS::style, "font-independent-line-spacing")); if (!fontIndependentLineSpacing.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { setLineSpacingFromFont(fontIndependentLineSpacing == "true"); } // Tabulators const QString tabStopDistance(styleStack.property(KoXmlNS::style, "tab-stop-distance")); if (!tabStopDistance.isEmpty()) { qreal stopDistance = KoUnit::parseValue(tabStopDistance); if (stopDistance >= 0) setTabStopDistance(stopDistance); } KoXmlElement tabStops(styleStack.childNode(KoXmlNS::style, "tab-stops")); if (!tabStops.isNull()) { // 3.11.10 QList tabList; KoXmlElement tabStop; forEachElement(tabStop, tabStops) { if(tabStop.localName() != "tab-stop") continue; // Tab position KoText::Tab tab; tab.position = KoUnit::parseValue(tabStop.attributeNS(KoXmlNS::style, "position", QString())); //debugText << "tab position " << tab.position; // Tab stop positions in the XML are relative to the left-margin // Equivalently, relative to the left end of our textshape // Tab type (left/right/center/char) const QString type = tabStop.attributeNS(KoXmlNS::style, "type", QString()); if (type == "center") tab.type = QTextOption::CenterTab; else if (type == "right") tab.type = QTextOption::RightTab; else if (type == "char") { tab.type = QTextOption::DelimiterTab; tab.delimiter = QChar('.'); } else //if ( type == "left" ) tab.type = QTextOption::LeftTab; // Tab delimiter char if (tab.type == QTextOption::DelimiterTab) { QString delimiterChar = tabStop.attributeNS(KoXmlNS::style, "char", QString()); // single character if (!delimiterChar.isEmpty()) { tab.delimiter = delimiterChar[0]; } else { // this is invalid. fallback to left-tabbing. tab.type = QTextOption::LeftTab; } } QString leaderType = tabStop.attributeNS(KoXmlNS::style, "leader-type", QString()); if (leaderType.isEmpty() || leaderType == "none") { tab.leaderType = KoCharacterStyle::NoLineType; } else { if (leaderType == "single") tab.leaderType = KoCharacterStyle::SingleLine; else if (leaderType == "double") tab.leaderType = KoCharacterStyle::DoubleLine; // change default leaderStyle tab.leaderStyle = KoCharacterStyle::SolidLine; } QString leaderStyle = tabStop.attributeNS(KoXmlNS::style, "leader-style", QString()); if (leaderStyle == "none") tab.leaderStyle = KoCharacterStyle::NoLineStyle; else if (leaderStyle == "solid") tab.leaderStyle = KoCharacterStyle::SolidLine; else if (leaderStyle == "dotted") tab.leaderStyle = KoCharacterStyle::DottedLine; else if (leaderStyle == "dash") tab.leaderStyle = KoCharacterStyle::DashLine; else if (leaderStyle == "long-dash") tab.leaderStyle = KoCharacterStyle::LongDashLine; else if (leaderStyle == "dot-dash") tab.leaderStyle = KoCharacterStyle::DotDashLine; else if (leaderStyle == "dot-dot-dash") tab.leaderStyle = KoCharacterStyle::DotDotDashLine; else if (leaderStyle == "wave") tab.leaderStyle = KoCharacterStyle::WaveLine; if (tab.leaderType == KoCharacterStyle::NoLineType && tab.leaderStyle != KoCharacterStyle::NoLineStyle) { if (leaderType == "none") // if leaderType was explicitly specified as none, but style was not none, // make leaderType override (ODF1.1 §15.5.11) tab.leaderStyle = KoCharacterStyle::NoLineStyle; else // if leaderType was implicitly assumed none, but style was not none, // make leaderStyle override tab.leaderType = KoCharacterStyle::SingleLine; } QString leaderColor = tabStop.attributeNS(KoXmlNS::style, "leader-color", QString()); if (leaderColor != "font-color") tab.leaderColor = QColor(leaderColor); // if invalid color (the default), will use text color QString width = tabStop.attributeNS(KoXmlNS::style, "leader-width", QString()); if (width.isEmpty() || width == "auto") tab.leaderWeight = KoCharacterStyle::AutoLineWeight; else if (width == "normal") tab.leaderWeight = KoCharacterStyle::NormalLineWeight; else if (width == "bold") tab.leaderWeight = KoCharacterStyle::BoldLineWeight; else if (width == "thin") tab.leaderWeight = KoCharacterStyle::ThinLineWeight; else if (width == "dash") tab.leaderWeight = KoCharacterStyle::DashLineWeight; else if (width == "medium") tab.leaderWeight = KoCharacterStyle::MediumLineWeight; else if (width == "thick") tab.leaderWeight = KoCharacterStyle::ThickLineWeight; else if (width.endsWith('%')) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = width.mid(0, width.length() - 1).toDouble(); } else if (width[width.length()-1].isNumber()) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = 100 * width.toDouble(); } else { tab.leaderWeight = KoCharacterStyle::LengthLineWeight; tab.leaderWidth = KoUnit::parseValue(width); } tab.leaderText = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); #if 0 else { // Fallback: convert leaderChar's unicode value QString leaderChar = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); if (!leaderChar.isEmpty()) { QChar ch = leaderChar[0]; switch (ch.latin1()) { case '.': tab.filling = TF_DOTS; break; case '-': case '_': // TODO in Words: differentiate --- and ___ tab.filling = TF_LINE; break; default: // Words doesn't have support for "any char" as filling. break; } } } #endif tabList.append(tab); } //for setTabPositions(tabList); } #if 0 layout.joinBorder = !(styleStack.property(KoXmlNS::style, "join-border") == "false"); #endif // Borders // The border attribute is actually three attributes in one string, all optional // and with no given order. Also there is a hierarchy, first the common for all // sides and then overwrites per side, while in the code only the sides are stored. // So first the common data border is fetched, then this is overwritten per // side and the result stored. const QString border(styleStack.property(KoXmlNS::fo, "border")); const ParagraphBorderData borderData = parseParagraphBorderData(border, ParagraphBorderData()); const QString borderLeft(styleStack.property(KoXmlNS::fo, "border-left")); const ParagraphBorderData leftParagraphBorderData = parseParagraphBorderData(borderLeft, borderData); if (leftParagraphBorderData.values & ParagraphBorderData::Width) { setLeftBorderWidth(leftParagraphBorderData.width); } if (leftParagraphBorderData.values & ParagraphBorderData::Style) { setLeftBorderStyle(leftParagraphBorderData.style); } if (leftParagraphBorderData.values & ParagraphBorderData::Color) { setLeftBorderColor(leftParagraphBorderData.color); } const QString borderTop(styleStack.property(KoXmlNS::fo, "border-top")); const ParagraphBorderData topParagraphBorderData = parseParagraphBorderData(borderTop, borderData); if (topParagraphBorderData.values & ParagraphBorderData::Width) { setTopBorderWidth(topParagraphBorderData.width); } if (topParagraphBorderData.values & ParagraphBorderData::Style) { setTopBorderStyle(topParagraphBorderData.style); } if (topParagraphBorderData.values & ParagraphBorderData::Color) { setTopBorderColor(topParagraphBorderData.color); } const QString borderRight(styleStack.property(KoXmlNS::fo, "border-right")); const ParagraphBorderData rightParagraphBorderData = parseParagraphBorderData(borderRight, borderData); if (rightParagraphBorderData.values & ParagraphBorderData::Width) { setRightBorderWidth(rightParagraphBorderData.width); } if (rightParagraphBorderData.values & ParagraphBorderData::Style) { setRightBorderStyle(rightParagraphBorderData.style); } if (rightParagraphBorderData.values & ParagraphBorderData::Color) { setRightBorderColor(rightParagraphBorderData.color); } const QString borderBottom(styleStack.property(KoXmlNS::fo, "border-bottom")); const ParagraphBorderData bottomParagraphBorderData = parseParagraphBorderData(borderBottom, borderData); if (bottomParagraphBorderData.values & ParagraphBorderData::Width) { setBottomBorderWidth(bottomParagraphBorderData.width); } if (bottomParagraphBorderData.values & ParagraphBorderData::Style) { setBottomBorderStyle(bottomParagraphBorderData.style); } if (bottomParagraphBorderData.values & ParagraphBorderData::Color) { setBottomBorderColor(bottomParagraphBorderData.color); } const QString borderLineWidthLeft(styleStack.property(KoXmlNS::style, "border-line-width", "left")); if (!borderLineWidthLeft.isEmpty()) { QStringList blw = borderLineWidthLeft.split(' ', QString::SkipEmptyParts); setLeftInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setLeftBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setLeftBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthTop(styleStack.property(KoXmlNS::style, "border-line-width", "top")); if (!borderLineWidthTop.isEmpty()) { QStringList blw = borderLineWidthTop.split(' ', QString::SkipEmptyParts); setTopInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setTopBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setTopBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthRight(styleStack.property(KoXmlNS::style, "border-line-width", "right")); if (!borderLineWidthRight.isEmpty()) { QStringList blw = borderLineWidthRight.split(' ', QString::SkipEmptyParts); setRightInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setRightBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setRightBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthBottom(styleStack.property(KoXmlNS::style, "border-line-width", "bottom")); if (!borderLineWidthBottom.isEmpty()) { QStringList blw = borderLineWidthBottom.split(' ', QString::SkipEmptyParts); setBottomInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setBottomBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setBottomBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } // drop caps KoXmlElement dropCap(styleStack.childNode(KoXmlNS::style, "drop-cap")); if (!dropCap.isNull()) { setDropCaps(true); const QString length = dropCap.attributeNS(KoXmlNS::style, "length", QString("1")); if (length.toLower() == "word") { setDropCapsLength(0); // 0 indicates drop caps of the whole first word } else { int l = length.toInt(); if (l > 0) // somefiles may use this to turn dropcaps off setDropCapsLength(length.toInt()); else setDropCaps(false); } const QString lines = dropCap.attributeNS(KoXmlNS::style, "lines", QString("1")); setDropCapsLines(lines.toInt()); const qreal distance = KoUnit::parseValue(dropCap.attributeNS(KoXmlNS::style, "distance", QString())); setDropCapsDistance(distance); const QString dropstyle = dropCap.attributeNS(KoXmlNS::style, "style-name"); if (! dropstyle.isEmpty()) { KoSharedLoadingData *sharedData = scontext.sharedData(KOTEXT_SHARED_LOADING_ID); KoTextSharedLoadingData *textSharedData = 0; textSharedData = dynamic_cast(sharedData); if (textSharedData) { KoCharacterStyle *cs = textSharedData->characterStyle(dropstyle, true); if (cs) setDropCapsTextStyleId(cs->styleId()); } } } // The fo:break-before and fo:break-after attributes insert a page or column break before or after a paragraph. const QString breakBefore(styleStack.property(KoXmlNS::fo, "break-before")); if (!breakBefore.isEmpty()) { setBreakBefore(KoText::textBreakFromString(breakBefore)); } const QString breakAfter(styleStack.property(KoXmlNS::fo, "break-after")); if (!breakAfter.isEmpty()) { setBreakAfter(KoText::textBreakFromString(breakAfter)); } const QString keepTogether(styleStack.property(KoXmlNS::fo, "keep-together")); if (!keepTogether.isEmpty()) { setNonBreakableLines(keepTogether == "always"); } const QString rawPageNumber(styleStack.property(KoXmlNS::style, "page-number")); if (!rawPageNumber.isEmpty()) { if (rawPageNumber == "auto") { setPageNumber(0); } else { bool ok; int number = rawPageNumber.toInt(&ok); if (ok) setPageNumber(number); } } // The fo:background-color attribute specifies the background color of a paragraph. const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color")); if (!bgcolor.isEmpty()) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } if (styleStack.hasProperty(KoXmlNS::style, "background-transparency")) { QString transparency = styleStack.property(KoXmlNS::style, "background-transparency"); bool ok = false; qreal transparencyValue = transparency.remove('%').toDouble(&ok); if (ok) { setBackgroundTransparency(transparencyValue/100); } } if (styleStack.hasProperty(KoXmlNS::style, "snap-to-layout-grid")) { setSnapToLayoutGrid(styleStack.property(KoXmlNS::style, "snap-to-layout-grid") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "register-true")) { setRegisterTrue(styleStack.property(KoXmlNS::style, "register-true") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "join-border")) { setJoinBorder(styleStack.property(KoXmlNS::style, "join-border") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "line-break")) { setStrictLineBreak(styleStack.property(KoXmlNS::style, "line-break") == "strict"); } // Support for an old non-standard OpenOffice attribute that we still find in too many documents... if (styleStack.hasProperty(KoXmlNS::text, "enable-numbering")) { setProperty(ForceDisablingList, styleStack.property(KoXmlNS::text, "enable-numbering") == "false"); } if (styleStack.hasProperty(KoXmlNS::fo, "orphans")) { bool ok = false; int orphans = styleStack.property(KoXmlNS::fo, "orphans").toInt(&ok); if (ok) setOrphanThreshold(orphans); } if (styleStack.hasProperty(KoXmlNS::fo, "widows")) { bool ok = false; int widows = styleStack.property(KoXmlNS::fo, "widows").toInt(&ok); if (ok) setWidowThreshold(widows); } if (styleStack.hasProperty(KoXmlNS::style, "justify-single-word")) { setJustifySingleWord(styleStack.property(KoXmlNS::style, "justify-single-word") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "writing-mode-automatic")) { setAutomaticWritingMode(styleStack.property(KoXmlNS::style, "writing-mode-automatic") == "true"); } if (styleStack.hasProperty(KoXmlNS::fo, "text-align-last")) { setAlignLastLine(KoText::alignmentFromString(styleStack.property(KoXmlNS::fo, "text-align-last"))); } if (styleStack.hasProperty(KoXmlNS::fo, "keep-with-next")) { setKeepWithNext(styleStack.property(KoXmlNS::fo, "keep-with-next") == "always"); } if (styleStack.hasProperty(KoXmlNS::style, "text-autospace")) { const QString autoSpace = styleStack.property(KoXmlNS::style, "text-autospace"); if (autoSpace == "none") setTextAutoSpace(NoAutoSpace); else if (autoSpace == "ideograph-alpha") setTextAutoSpace(IdeographAlpha); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-keep")) { setKeepHyphenation(styleStack.property(KoXmlNS::fo, "hyphenation-keep") == "page"); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-ladder-count")) { QString ladderCount = styleStack.property(KoXmlNS::fo, "hyphenation-ladder-count"); if (ladderCount == "no-limit") setHyphenationLadderCount(0); else { bool ok; int value = ladderCount.toInt(&ok); if ((ok) && (value > 0)) setHyphenationLadderCount(value); } } if (styleStack.hasProperty(KoXmlNS::style, "punctuation-wrap")) { setPunctuationWrap(styleStack.property(KoXmlNS::style, "punctuation-wrap") == "simple"); } if (styleStack.hasProperty(KoXmlNS::style, "vertical-align")) { const QString valign = styleStack.property(KoXmlNS::style, "vertical-align"); if (valign == "auto") setVerticalAlignment(VAlignAuto); else if (valign == "baseline") setVerticalAlignment(VAlignBaseline); else if (valign == "bottom") setVerticalAlignment(VAlignBottom); else if (valign == "middle") setVerticalAlignment(VAlignMiddle); else if (valign == "top") setVerticalAlignment(VAlignTop); } if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) setShadow(shadow); } //following properties KoParagraphStyle provides us are not handled now; // LineSpacingFromFont, // FollowDocBaseline, } void KoParagraphStyle::setTabPositions(const QList &tabs) { QList newTabs = tabs; std::sort(newTabs.begin(), newTabs.end(), compareTabs); QList list; Q_FOREACH (const KoText::Tab &tab, tabs) { QVariant v; v.setValue(tab); list.append(v); } setProperty(TabPositions, list); } QList KoParagraphStyle::tabPositions() const { QVariant variant = value(TabPositions); if (variant.isNull()) return QList(); QList answer; Q_FOREACH (const QVariant &tab, qvariant_cast >(variant)) { answer.append(tab.value()); } return answer; } void KoParagraphStyle::setTabStopDistance(qreal value) { setProperty(TabStopDistance, value); } qreal KoParagraphStyle::tabStopDistance() const { return propertyDouble(TabStopDistance); } bool KoParagraphStyle::registerTrue() const { if (hasProperty(RegisterTrue)) return propertyBoolean(RegisterTrue); return false; } void KoParagraphStyle::setRegisterTrue(bool value) { setProperty(RegisterTrue, value); } bool KoParagraphStyle::strictLineBreak() const { if (hasProperty(StrictLineBreak)) return propertyBoolean(StrictLineBreak); return false; } void KoParagraphStyle::setStrictLineBreak(bool value) { setProperty(StrictLineBreak, value); } bool KoParagraphStyle::justifySingleWord() const { if (hasProperty(JustifySingleWord)) return propertyBoolean(JustifySingleWord); return false; } void KoParagraphStyle::setJustifySingleWord(bool value) { setProperty(JustifySingleWord, value); } void KoParagraphStyle::setTextAutoSpace(KoParagraphStyle::AutoSpace value) { setProperty(TextAutoSpace, value); } KoParagraphStyle::AutoSpace KoParagraphStyle::textAutoSpace() const { if (hasProperty(TextAutoSpace)) return static_cast(propertyInt(TextAutoSpace)); return NoAutoSpace; } void KoParagraphStyle::copyProperties(const KoParagraphStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change KoCharacterStyle::copyProperties(style); d->parentStyle = style->d->parentStyle; d->defaultStyle = style->d->defaultStyle; } KoParagraphStyle *KoParagraphStyle::clone(QObject *parent) const { KoParagraphStyle *newStyle = new KoParagraphStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoParagraphStyle::compareParagraphProperties(const KoParagraphStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } bool KoParagraphStyle::operator==(const KoParagraphStyle &other) const { if (!compareParagraphProperties(other)) return false; if (!compareCharacterProperties(other)) return false; return true; } void KoParagraphStyle::removeDuplicates(const KoParagraphStyle &other) { d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); KoCharacterStyle::removeDuplicates(other); } void KoParagraphStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) const { bool writtenLineSpacing = false; KoCharacterStyle::saveOdf(style); if (listStyle()) { KoGenStyle liststyle(KoGenStyle::ListStyle); listStyle()->saveOdf(liststyle, context); QString name(QString(QUrl::toPercentEncoding(listStyle()->name(), "", " ")).replace('%', '_')); if (name.isEmpty()) name = 'L'; style.addAttribute("style:list-style-name", context.mainStyles().insert(liststyle, name, KoGenStyles::DontAddNumberToName)); } // only custom style have a displayname. automatic styles don't have a name set. if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList keys = d->stylesPrivate.keys(); if (keys.contains(KoParagraphStyle::LeftPadding) && keys.contains(KoParagraphStyle::RightPadding) && keys.contains(KoParagraphStyle::TopPadding) && keys.contains(KoParagraphStyle::BottomPadding)) { if ((leftPadding() == rightPadding()) && (topPadding() == bottomPadding()) && (rightPadding() == topPadding())) { style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::ParagraphType); keys.removeOne(KoParagraphStyle::LeftPadding); keys.removeOne(KoParagraphStyle::RightPadding); keys.removeOne(KoParagraphStyle::TopPadding); keys.removeOne(KoParagraphStyle::BottomPadding); } } if (keys.contains(QTextFormat::BlockLeftMargin) && keys.contains(QTextFormat::BlockRightMargin) && keys.contains(QTextFormat::BlockBottomMargin) && keys.contains(QTextFormat::BlockTopMargin)) { if ((leftMargin() == rightMargin()) && (topMargin() == bottomMargin()) && (rightMargin() == topMargin())) { style.addPropertyLength("fo:margin", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); keys.removeOne(QTextFormat::BlockLeftMargin); keys.removeOne(QTextFormat::BlockRightMargin); keys.removeOne(QTextFormat::BlockTopMargin); keys.removeOne(QTextFormat::BlockBottomMargin); } } foreach (int key, keys) { if (key == QTextFormat::BlockAlignment) { int alignValue = 0; bool ok = false; alignValue = d->stylesPrivate.value(key).toInt(&ok); if (ok) { Qt::Alignment alignment = (Qt::Alignment) alignValue; QString align = KoText::alignmentToString(alignment); if (!align.isEmpty()) style.addProperty("fo:text-align", align, KoGenStyle::ParagraphType); } } else if (key == KoParagraphStyle::AlignLastLine) { QString align = KoText::alignmentToString(alignLastLine()); if (!align.isEmpty()) style.addProperty("fo:text-align-last", align, KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextProgressionDirection) { style.addProperty("style:writing-mode", KoText::directionToString(textProgressionDirection()), KoGenStyle::ParagraphType); } else if (key == LineNumbering) { style.addProperty("text:number-lines", lineNumbering()); } else if (key == PageNumber) { if (pageNumber() == 0) style.addProperty("style:page-number", "auto", KoGenStyle::ParagraphType); else style.addProperty("style:page-number", pageNumber(), KoGenStyle::ParagraphType); } else if (key == LineNumberStartValue) { style.addProperty("text:line-number", lineNumberStartValue()); } else if (key == BreakAfter) { style.addProperty("fo:break-after", KoText::textBreakToString(breakAfter()), KoGenStyle::ParagraphType); } else if (key == BreakBefore) { style.addProperty("fo:break-before", KoText::textBreakToString(breakBefore()), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockNonBreakableLines) { if (nonBreakableLines()) { style.addProperty("fo:keep-together", "always", KoGenStyle::ParagraphType); } else { style.addProperty("fo:keep-together", "auto", KoGenStyle::ParagraphType); } } else if (key == QTextFormat::BackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::ParagraphType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BackgroundTransparency) { style.addProperty("style:background-transparency", QString("%1%").arg(backgroundTransparency() * 100), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::SnapToLayoutGrid) { style.addProperty("style:snap-to-layout-grid", snapToLayoutGrid(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::JustifySingleWord) { style.addProperty("style:justify-single-word", justifySingleWord(), KoGenStyle::ParagraphType); } else if (key == RegisterTrue) { style.addProperty("style:register-true", registerTrue(), KoGenStyle::ParagraphType); } else if (key == StrictLineBreak) { if (strictLineBreak()) style.addProperty("style:line-break", "strict", KoGenStyle::ParagraphType); else style.addProperty("style:line-break", "normal", KoGenStyle::ParagraphType); } else if (key == JoinBorder) { style.addProperty("style:join-border", joinBorder(), KoGenStyle::ParagraphType); } else if (key == OrphanThreshold) { style.addProperty("fo:orphans", orphanThreshold(), KoGenStyle::ParagraphType); } else if (key == WidowThreshold) { style.addProperty("fo:widows", widowThreshold(), KoGenStyle::ParagraphType); // Padding } else if (key == KoParagraphStyle::LeftPadding) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::RightPadding) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TopPadding) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BottomPadding) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::ParagraphType); // Margin } else if (key == QTextFormat::BlockLeftMargin) { style.addPropertyLength("fo:margin-left", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockRightMargin) { style.addPropertyLength("fo:margin-right", propertyLength(QTextFormat::BlockRightMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockTopMargin) { style.addPropertyLength("fo:margin-top", propertyLength(QTextFormat::BlockTopMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockBottomMargin) { style.addPropertyLength("fo:margin-bottom", propertyLength(QTextFormat::BlockBottomMargin), KoGenStyle::ParagraphType); // Line spacing } else if ( key == KoParagraphStyle::MinimumLineHeight || key == KoParagraphStyle::LineSpacing || key == KoParagraphStyle::PercentLineHeight || key == KoParagraphStyle::FixedLineHeight || key == KoParagraphStyle::LineSpacingFromFont) { if (key == KoParagraphStyle::MinimumLineHeight && propertyLength(MinimumLineHeight).rawValue() != 0) { style.addPropertyLength("style:line-height-at-least", propertyLength(MinimumLineHeight), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacing && lineSpacing() != 0) { style.addPropertyPt("style:line-spacing", lineSpacing(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::PercentLineHeight && lineHeightPercent() != 0) { style.addProperty("fo:line-height", QString("%1%").arg(lineHeightPercent()), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::FixedLineHeight && lineHeightAbsolute() != 0) { style.addPropertyPt("fo:line-height", lineHeightAbsolute(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacingFromFont && lineHeightAbsolute() == 0) { style.addProperty("style:font-independent-line-spacing", lineSpacingFromFont(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } // } else if (key == QTextFormat::TextIndent) { style.addPropertyLength("fo:text-indent", propertyLength(QTextFormat::TextIndent), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::AutoTextIndent) { style.addProperty("style:auto-text-indent", autoTextIndent(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TabStopDistance) { style.addPropertyPt("style:tab-stop-distance", tabStopDistance(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::MasterPageName) { style.addAttribute("style:master-page-name", masterPageName()); } else if (key == KoParagraphStyle::DefaultOutlineLevel) { style.addAttribute("style:default-outline-level", defaultOutlineLevel()); } else if (key == KoParagraphStyle::AutomaticWritingMode) { style.addProperty("style:writing-mode-automatic", automaticWritingMode(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextAutoSpace) { if (textAutoSpace() == NoAutoSpace) style.addProperty("style:text-autospace", "none", KoGenStyle::ParagraphType); else if (textAutoSpace() == IdeographAlpha) style.addProperty("style:text-autospace", "ideograph-alpha", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepWithNext) { if (keepWithNext()) style.addProperty("fo:keep-with-next", "always", KoGenStyle::ParagraphType); else style.addProperty("fo:keep-with-next", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepHyphenation) { if (keepHyphenation()) style.addProperty("fo:hyphenation-keep", "page", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-keep", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::HyphenationLadderCount) { int value = hyphenationLadderCount(); if (value == 0) style.addProperty("fo:hyphenation-ladder-count", "no-limit", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-ladder-count", value, KoGenStyle::ParagraphType); } else if (key == PunctuationWrap) { if (punctuationWrap()) style.addProperty("style:punctuation-wrap", "simple", KoGenStyle::ParagraphType); else style.addProperty("style:punctuation-wrap", "hanging", KoGenStyle::ParagraphType); } else if (key == VerticalAlignment) { VerticalAlign valign = verticalAlignment(); if (valign == VAlignAuto) style.addProperty("style:vertical-align", "auto", KoGenStyle::ParagraphType); else if (valign == VAlignBaseline) style.addProperty("style:vertical-align", "baseline", KoGenStyle::ParagraphType); else if (valign == VAlignBottom) style.addProperty("style:vertical-align", "bottom", KoGenStyle::ParagraphType); else if (valign == VAlignMiddle) style.addProperty("style:vertical-align", "middle", KoGenStyle::ParagraphType); else if (valign == VAlignTop) style.addProperty("style:vertical-align", "top", KoGenStyle::ParagraphType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (!writtenLineSpacing && propertyBoolean(NormalLineHeight)) style.addProperty("fo:line-height", QString("normal"), KoGenStyle::ParagraphType); // save border stuff QString leftBorder = QString("%1pt %2 %3").arg(QString::number(leftBorderWidth()), KoBorder::odfBorderStyleString(leftBorderStyle()), leftBorderColor().name()); QString rightBorder = QString("%1pt %2 %3").arg(QString::number(rightBorderWidth()), KoBorder::odfBorderStyleString(rightBorderStyle()), rightBorderColor().name()); QString topBorder = QString("%1pt %2 %3").arg(QString::number(topBorderWidth()), KoBorder::odfBorderStyleString(topBorderStyle()), topBorderColor().name()); QString bottomBorder = QString("%1pt %2 %3").arg(QString::number(bottomBorderWidth()), KoBorder::odfBorderStyleString(bottomBorderStyle()), bottomBorderColor().name()); if (leftBorder == rightBorder && leftBorder == topBorder && leftBorder == bottomBorder) { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border", leftBorder, KoGenStyle::ParagraphType); } else { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-left", leftBorder, KoGenStyle::ParagraphType); if (rightBorderWidth() > 0 && rightBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-right", rightBorder, KoGenStyle::ParagraphType); if (topBorderWidth() > 0 && topBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-top", topBorder, KoGenStyle::ParagraphType); if (bottomBorderWidth() > 0 && bottomBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-bottom", bottomBorder, KoGenStyle::ParagraphType); } QString leftBorderLineWidth, rightBorderLineWidth, topBorderLineWidth, bottomBorderLineWidth; if (leftBorderStyle() == KoBorder::BorderDouble) leftBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(leftInnerBorderWidth()), QString::number(leftBorderSpacing()), QString::number(leftBorderWidth())); if (rightBorderStyle() == KoBorder::BorderDouble) rightBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(rightInnerBorderWidth()), QString::number(rightBorderSpacing()), QString::number(rightBorderWidth())); if (topBorderStyle() == KoBorder::BorderDouble) topBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(topInnerBorderWidth()), QString::number(topBorderSpacing()), QString::number(topBorderWidth())); if (bottomBorderStyle() == KoBorder::BorderDouble) bottomBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(bottomInnerBorderWidth()), QString::number(bottomBorderSpacing()), QString::number(bottomBorderWidth())); if (leftBorderLineWidth == rightBorderLineWidth && leftBorderLineWidth == topBorderLineWidth && leftBorderLineWidth == bottomBorderLineWidth && !leftBorderLineWidth.isEmpty()) { style.addProperty("style:border-line-width", leftBorderLineWidth, KoGenStyle::ParagraphType); } else { if (!leftBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-left", leftBorderLineWidth, KoGenStyle::ParagraphType); if (!rightBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-right", rightBorderLineWidth, KoGenStyle::ParagraphType); if (!topBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-top", topBorderLineWidth, KoGenStyle::ParagraphType); if (!bottomBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, KoGenStyle::ParagraphType); } const int indentation = 4; // indentation for children of office:styles/style:style/style:paragraph-properties // drop-caps if (dropCaps()) { QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:drop-cap"); elementWriter.addAttribute("style:lines", QString::number(dropCapsLines())); elementWriter.addAttribute("style:length", dropCapsLength() == 0 ? "word" : QString::number(dropCapsLength())); if (dropCapsDistance()) - elementWriter.addAttributePt("style:distance", dropCapsDistance()); + elementWriter.addAttribute("style:distance", dropCapsDistance()); elementWriter.endElement(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:drop-cap", elementContents, KoGenStyle::ParagraphType); } if (tabPositions().count() > 0) { QMap tabTypeMap, leaderTypeMap, leaderStyleMap, leaderWeightMap; tabTypeMap[QTextOption::LeftTab] = "left"; tabTypeMap[QTextOption::RightTab] = "right"; tabTypeMap[QTextOption::CenterTab] = "center"; tabTypeMap[QTextOption::DelimiterTab] = "char"; leaderTypeMap[KoCharacterStyle::NoLineType] = "none"; leaderTypeMap[KoCharacterStyle::SingleLine] = "single"; leaderTypeMap[KoCharacterStyle::DoubleLine] = "double"; leaderStyleMap[KoCharacterStyle::NoLineStyle] = "none"; leaderStyleMap[KoCharacterStyle::SolidLine] = "solid"; leaderStyleMap[KoCharacterStyle::DottedLine] = "dotted"; leaderStyleMap[KoCharacterStyle::DashLine] = "dash"; leaderStyleMap[KoCharacterStyle::LongDashLine] = "long-dash"; leaderStyleMap[KoCharacterStyle::DotDashLine] = "dot-dash"; leaderStyleMap[KoCharacterStyle::DotDotDashLine] = "dot-dot-dash"; leaderStyleMap[KoCharacterStyle::WaveLine] = "wave"; leaderWeightMap[KoCharacterStyle::AutoLineWeight] = "auto"; leaderWeightMap[KoCharacterStyle::NormalLineWeight] = "normal"; leaderWeightMap[KoCharacterStyle::BoldLineWeight] = "bold"; leaderWeightMap[KoCharacterStyle::ThinLineWeight] = "thin"; leaderWeightMap[KoCharacterStyle::DashLineWeight] = "dash"; leaderWeightMap[KoCharacterStyle::MediumLineWeight] = "medium"; leaderWeightMap[KoCharacterStyle::ThickLineWeight] = "thick"; QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:tab-stops"); Q_FOREACH (const KoText::Tab &tab, tabPositions()) { elementWriter.startElement("style:tab-stop"); - elementWriter.addAttributePt("style:position", tab.position); + elementWriter.addAttribute("style:position", tab.position); if (!tabTypeMap[tab.type].isEmpty()) elementWriter.addAttribute("style:type", tabTypeMap[tab.type]); if (tab.type == QTextOption::DelimiterTab && !tab.delimiter.isNull()) elementWriter.addAttribute("style:char", tab.delimiter); if (!leaderTypeMap[tab.leaderType].isEmpty()) elementWriter.addAttribute("style:leader-type", leaderTypeMap[tab.leaderType]); if (!leaderStyleMap[tab.leaderStyle].isEmpty()) elementWriter.addAttribute("style:leader-style", leaderStyleMap[tab.leaderStyle]); if (!leaderWeightMap[tab.leaderWeight].isEmpty()) elementWriter.addAttribute("style:leader-width", leaderWeightMap[tab.leaderWeight]); else if (tab.leaderWeight == KoCharacterStyle::PercentLineWeight) elementWriter.addAttribute("style:leader-width", QString("%1%").arg(QString::number(tab.leaderWidth))); else if (tab.leaderWeight == KoCharacterStyle::LengthLineWeight) - elementWriter.addAttributePt("style:leader-width", tab.leaderWidth); + elementWriter.addAttribute("style:leader-width", tab.leaderWidth); if (tab.leaderColor.isValid()) elementWriter.addAttribute("style:leader-color", tab.leaderColor.name()); else elementWriter.addAttribute("style:leader-color", "font-color"); if (!tab.leaderText.isEmpty()) elementWriter.addAttribute("style:leader-text", tab.leaderText); elementWriter.endElement(); } elementWriter.endElement(); buf.close(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:tab-stops", elementContents, KoGenStyle::ParagraphType); } } bool KoParagraphStyle::hasDefaults() const { int size=d->stylesPrivate.properties().size(); if ((size == 0) || (size==1 && d->stylesPrivate.properties().contains(StyleId))) { return true; } return false; } KoList *KoParagraphStyle::list() const { return d->list; } diff --git a/plugins/flake/textshape/kotext/styles/KoSectionStyle.cpp b/plugins/flake/textshape/kotext/styles/KoSectionStyle.cpp index 544ff5af4e..e2ac72de8d 100644 --- a/plugins/flake/textshape/kotext/styles/KoSectionStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoSectionStyle.cpp @@ -1,573 +1,573 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 KO GmbH * Copyright 2012 Friedrich W. H. Kossebau * * 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 "KoSectionStyle.h" #include #include "Styles_p.h" #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" Q_DECLARE_METATYPE(QList) class Q_DECL_HIDDEN KoSectionStyle::Private { public: Private() : parentStyle(0) {} ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } int propertyInt(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool propertyBoolean(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) return false; return variant.toBool(); } qreal propertyDouble(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QColor propertyColor(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) return QColor(); return variant.value(); } QList propertyColumnData() const{ QVariant variant = stylesPrivate.value(ColumnData); if (variant.isNull()) return QList(); return variant.value >(); } QString name; KoSectionStyle *parentStyle; StylePrivate stylesPrivate; }; KoSectionStyle::KoSectionStyle(QObject *parent) : QObject(parent), d(new Private()) { } KoSectionStyle::KoSectionStyle(const QTextFrameFormat §ionFormat, QObject *parent) : QObject(parent), d(new Private()) { d->stylesPrivate = sectionFormat.properties(); } KoSectionStyle::~KoSectionStyle() { delete d; } void KoSectionStyle::setParentStyle(KoSectionStyle *parent) { d->parentStyle = parent; } void KoSectionStyle::setProperty(int key, const QVariant &value) { if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoSectionStyle::remove(int key) { d->stylesPrivate.remove(key); } QVariant KoSectionStyle::value(int key) const { QVariant var = d->stylesPrivate.value(key); if (var.isNull() && d->parentStyle) var = d->parentStyle->value(key); return var; } bool KoSectionStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } void KoSectionStyle::applyStyle(QTextFrameFormat &format) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); format.setProperty(keys[i], variant); } } void KoSectionStyle::applyStyle(QTextFrame §ion) const { QTextFrameFormat format = section.frameFormat(); applyStyle(format); section.setFrameFormat(format); } void KoSectionStyle::unapplyStyle(QTextFrame §ion) const { if (d->parentStyle) d->parentStyle->unapplyStyle(section); QTextFrameFormat format = section.frameFormat(); QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); if (variant == format.property(keys[i])) format.clearProperty(keys[i]); } section.setFrameFormat(format); } void KoSectionStyle::setLeftMargin(qreal margin) { setProperty(QTextFormat::BlockLeftMargin, margin); } qreal KoSectionStyle::leftMargin() const { return d->propertyDouble(QTextFormat::BlockLeftMargin); } void KoSectionStyle::setRightMargin(qreal margin) { setProperty(QTextFormat::BlockRightMargin, margin); } qreal KoSectionStyle::rightMargin() const { return d->propertyDouble(QTextFormat::BlockRightMargin); } void KoSectionStyle::setColumnCount(int columnCount) { setProperty(ColumnCount, columnCount); } int KoSectionStyle::columnCount() const { return d->propertyInt(ColumnCount); } void KoSectionStyle::setColumnGapWidth(qreal columnGapWidth) { setProperty(ColumnGapWidth, columnGapWidth); } qreal KoSectionStyle::columnGapWidth() const { return d->propertyDouble(ColumnGapWidth); } void KoSectionStyle::setColumnData(const QList &columnData) { setProperty(ColumnData, QVariant::fromValue >(columnData)); } QList KoSectionStyle::columnData() const { return d->propertyColumnData(); } void KoSectionStyle::setSeparatorStyle(KoColumns::SeparatorStyle separatorStyle) { setProperty(SeparatorStyle, separatorStyle); } KoColumns::SeparatorStyle KoSectionStyle::separatorStyle() const { return static_cast(d->propertyInt(SeparatorStyle)); } void KoSectionStyle::setSeparatorColor(const QColor &separatorColor) { setProperty(SeparatorColor, separatorColor); } QColor KoSectionStyle::separatorColor() const { return d->propertyColor(SeparatorColor); } void KoSectionStyle::setSeparatorWidth(qreal separatorWidth) { setProperty(SeparatorWidth, separatorWidth); } qreal KoSectionStyle::separatorWidth() const { return d->propertyDouble(SeparatorWidth); } void KoSectionStyle::setSeparatorHeight( int separatorHeight) { setProperty(SeparatorHeight, separatorHeight); } int KoSectionStyle::separatorHeight() const { return d->propertyInt(SeparatorHeight); } void KoSectionStyle::setSeparatorVerticalAlignment(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment) { setProperty(SeparatorVerticalAlignment, separatorVerticalAlignment); } KoColumns::SeparatorVerticalAlignment KoSectionStyle::separatorVerticalAlignment() const { return static_cast(d->propertyInt(SeparatorVerticalAlignment)); } KoSectionStyle *KoSectionStyle::parentStyle() const { return d->parentStyle; } QString KoSectionStyle::name() const { return d->name; } void KoSectionStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoSectionStyle::styleId() const { return d->propertyInt(StyleId); } void KoSectionStyle::setStyleId(int id) { setProperty(StyleId, id); } KoText::Direction KoSectionStyle::textProgressionDirection() const { return static_cast(d->propertyInt(TextProgressionDirection)); } void KoSectionStyle::setTextProgressionDirection(KoText::Direction dir) { setProperty(TextProgressionDirection, dir); } void KoSectionStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoSectionStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoSectionStyle::background() const { QVariant variant = d->stylesPrivate.value(QTextFormat::BackgroundBrush); if (variant.isNull()) { QBrush brush; return brush; } return qvariant_cast(variant); } void KoSectionStyle::loadOdf(const KoXmlElement *element, KoOdfLoadingContext &context) { if (element->hasAttributeNS(KoXmlNS::style, "display-name")) d->name = element->attributeNS(KoXmlNS::style, "display-name", QString()); if (d->name.isEmpty()) // if no style:display-name is given us the style:name d->name = element->attributeNS(KoXmlNS::style, "name", QString()); context.styleStack().save(); // Load all parents - only because we don't support inheritance. QString family = element->attributeNS(KoXmlNS::style, "family", "section"); context.addStyles(element, family.toLocal8Bit().constData()); // Load all parents - only because we don't support inheritance. context.styleStack().setTypeProperties("section"); // load all style attributes from "style:section-properties" KoStyleStack &styleStack = context.styleStack(); // in 1.6 this was defined at KoParagLayout::loadOasisParagLayout(KoParagLayout&, KoOasisContext&) if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) { // http://www.w3.org/TR/2004/WD-xsl11-20041216/#writing-mode QString writingMode = styleStack.property(KoXmlNS::style, "writing-mode"); setTextProgressionDirection(KoText::directionFromString(writingMode)); } // Indentation (margin) bool hasMarginLeft = styleStack.hasProperty(KoXmlNS::fo, "margin-left"); bool hasMarginRight = styleStack.hasProperty(KoXmlNS::fo, "margin-right"); if (hasMarginLeft) setLeftMargin(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-left"))); if (hasMarginRight) setRightMargin(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "margin-right"))); // The fo:background-color attribute specifies the background color of a paragraph. if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } if (styleStack.hasChildNode(KoXmlNS::style, "columns")) { KoXmlElement columns = styleStack.childNode(KoXmlNS::style, "columns"); int columnCount = columns.attributeNS(KoXmlNS::fo, "column-count").toInt(); if (columnCount < 1) columnCount = 1; setColumnCount(columnCount); if (styleStack.hasProperty(KoXmlNS::fo, "column-gap")) { setColumnGapWidth(KoUnit::parseValue(columns.attributeNS(KoXmlNS::fo, "column-gap"))); } else { QList columnData; KoXmlElement columnElement; forEachElement(columnElement, columns) { if(columnElement.localName() != QLatin1String("column") || columnElement.namespaceURI() != KoXmlNS::style) continue; KoColumns::ColumnDatum datum; datum.leftMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "start-indent"), 0.0); datum.rightMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "end-indent"), 0.0); datum.topMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-before"), 0.0); datum.bottomMargin = KoUnit::parseValue(columnElement.attributeNS(KoXmlNS::fo, "space-after"), 0.0); datum.relativeWidth = KoColumns::parseRelativeWidth(columnElement.attributeNS(KoXmlNS::style, "rel-width")); // on a bad relativeWidth just drop all data if (datum.relativeWidth <= 0) { columnData.clear(); break; } columnData.append(datum); } if (! columnData.isEmpty()) { setColumnData(columnData); } } KoXmlElement columnSep = KoXml::namedItemNS(columns, KoXmlNS::style, "column-sep"); if (! columnSep.isNull()) { if (columnSep.hasAttributeNS(KoXmlNS::style, "style")) setSeparatorStyle(KoColumns::parseSeparatorStyle(columnSep.attributeNS(KoXmlNS::style, "style"))); if (columnSep.hasAttributeNS(KoXmlNS::style, "width")) setSeparatorWidth(KoUnit::parseValue(columnSep.attributeNS(KoXmlNS::style, "width"))); if (columnSep.hasAttributeNS(KoXmlNS::style, "height")) setSeparatorHeight(KoColumns::parseSeparatorHeight(columnSep.attributeNS(KoXmlNS::style, "height"))); if (columnSep.hasAttributeNS(KoXmlNS::style, "color")) setSeparatorColor(KoColumns::parseSeparatorColor(columnSep.attributeNS(KoXmlNS::style, "color"))); if (columnSep.hasAttributeNS(KoXmlNS::style, "vertical-align")) setSeparatorVerticalAlignment( KoColumns::parseSeparatorVerticalAlignment(columnSep.attributeNS(KoXmlNS::style, "vertical-align"))); } } styleStack.restore(); } void KoSectionStyle::copyProperties(const KoSectionStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change d->parentStyle = style->d->parentStyle; } KoSectionStyle *KoSectionStyle::clone(QObject *parent) const { KoSectionStyle *newStyle = new KoSectionStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoSectionStyle::operator==(const KoSectionStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } void KoSectionStyle::removeDuplicates(const KoSectionStyle &other) { d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); } void KoSectionStyle::saveOdf(KoGenStyle &style) { // only custom style have a displayname. automatic styles don't have a name set. if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList columnsKeys; QList keys = d->stylesPrivate.keys(); Q_FOREACH (int key, keys) { switch (key) { case KoSectionStyle::TextProgressionDirection: { int directionValue = 0; bool ok = false; directionValue = d->stylesPrivate.value(key).toInt(&ok); if (ok) { QString direction; if (directionValue == KoText::LeftRightTopBottom) direction = "lr-tb"; else if (directionValue == KoText::RightLeftTopBottom) direction = "rl-tb"; else if (directionValue == KoText::TopBottomRightLeft) direction = "tb-lr"; else if (directionValue == KoText::InheritDirection) direction = "page"; if (!direction.isEmpty()) style.addProperty("style:writing-mode", direction, KoGenStyle::DefaultType); } break; } case QTextFormat::BackgroundBrush: { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::ParagraphType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::DefaultType); break; } case QTextFormat::BlockLeftMargin: style.addPropertyPt("fo:margin-left", leftMargin(), KoGenStyle::DefaultType); break; case QTextFormat::BlockRightMargin: style.addPropertyPt("fo:margin-right", rightMargin(), KoGenStyle::DefaultType); break; case ColumnCount: case ColumnGapWidth: case SeparatorStyle: case SeparatorColor: case SeparatorVerticalAlignment: case SeparatorWidth: case SeparatorHeight: columnsKeys.append(key); break; } } if (!columnsKeys.isEmpty()) { QBuffer buffer; buffer.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buffer); // TODO pass indentation level elementWriter.startElement("style:columns"); // seems these two are mandatory elementWriter.addAttribute("fo:column-count", columnCount()); - elementWriter.addAttributePt("fo:column-gap", columnGapWidth()); + elementWriter.addAttribute("fo:column-gap", columnGapWidth()); columnsKeys.removeOne(ColumnCount); columnsKeys.removeOne(ColumnGapWidth); if (!columnsKeys.isEmpty()) { elementWriter.startElement("style:column-sep"); Q_FOREACH (int key, columnsKeys) { switch (key) { case SeparatorStyle: elementWriter.addAttribute("style:style", KoColumns::separatorStyleString(separatorStyle())); break; case SeparatorColor: elementWriter.addAttribute("style:color", separatorColor().name()); break; case SeparatorVerticalAlignment: elementWriter.addAttribute("style:vertical-align", KoColumns::separatorVerticalAlignmentString(separatorVerticalAlignment())); break; case SeparatorWidth: - elementWriter.addAttributePt("style:width", + elementWriter.addAttribute("style:width", separatorWidth()); break; case SeparatorHeight: elementWriter.addAttribute("style:height", QString::fromLatin1("%1%").arg(separatorHeight())); break; } } elementWriter.endElement(); // style:column-sep } elementWriter.endElement(); // style:columns const QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size()); style.addChildElement("style:columns", elementContents); } } diff --git a/plugins/impex/exr/tests/kis_exr_test.cpp b/plugins/impex/exr/tests/kis_exr_test.cpp index c29ad6732b..53b5600c4b 100644 --- a/plugins/impex/exr/tests/kis_exr_test.cpp +++ b/plugins/impex/exr/tests/kis_exr_test.cpp @@ -1,95 +1,94 @@ /* * 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_exr_test.h" - - #include #include -#include +#include + #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisExrTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } void KisExrTest::testRoundTrip() { QString inputFileName(TestUtil::fetchDataFileLazy("CandleGlass.exr")); KisDocument *doc1 = KisPart::instance()->createDocument(); KisImportExportManager manager(doc1); doc1->setFileBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(inputFileName, QString()); QCOMPARE(status, KisImportExportFilter::OK); QVERIFY(doc1->image()); QTemporaryFile savedFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".exr")); savedFile.setAutoRemove(false); savedFile.open(); QString savedFileName(savedFile.fileName()); QString typeName = KisMimeDatabase::mimeTypeForFile(savedFileName, false); QByteArray mimeType(typeName.toLatin1()); status = manager.exportDocument(savedFileName, savedFileName, mimeType); QVERIFY(QFileInfo(savedFileName).exists()); { KisDocument *doc2 = KisPart::instance()->createDocument(); KisImportExportManager manager(doc2); doc2->setFileBatchMode(true); status = manager.importDocument(savedFileName, QString()); QCOMPARE(status, KisImportExportFilter::OK); QVERIFY(doc2->image()); QVERIFY(TestUtil::comparePaintDevicesClever( doc1->image()->root()->firstChild()->paintDevice(), doc2->image()->root()->firstChild()->paintDevice(), 0.01 /* meaningless alpha */)); delete doc2; } savedFile.close(); delete doc1; } -QTEST_MAIN(KisExrTest) +KISTEST_MAIN(KisExrTest) diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp index a7134db277..e2052add32 100644 --- a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp +++ b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp @@ -1,36 +1,38 @@ /* * Copyright (c) 2017 Victor Wåhlström * * 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_heightmap_test.h" #include #include +#include + #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisHeightmapTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } -QTEST_MAIN(KisHeightmapTest) +KISTEST_MAIN(KisHeightmapTest) diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp index 46d6f59502..884696c91a 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp +++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp @@ -1,53 +1,54 @@ /* * 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_jpeg_test.h" #include #include #include "kisexiv2/kis_exiv2.h" #include "filestest.h" #include "jpeglib.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 #ifndef JPEG_LIB_VERSION #error "JPEG_LIB_VERSION not set. libjpeg should set it." #endif void KisJpegTest::testFiles() { KisExiv2::initialize(); /** * Different versions of JPEG library may produce a bit different * result, so just compare in a weak way */ const int fuzziness = 3; if (JPEG_LIB_VERSION == 80){ TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness); }else { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness); } } -QTEST_MAIN(KisJpegTest) +KISTEST_MAIN(KisJpegTest) diff --git a/plugins/impex/libkra/tests/data/load_test.kra b/plugins/impex/libkra/tests/data/load_test.kra index 7beeb9af44..fb1ab88d5a 100644 Binary files a/plugins/impex/libkra/tests/data/load_test.kra and b/plugins/impex/libkra/tests/data/load_test.kra differ diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp index a540c80389..6ab88825ea 100644 --- a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp @@ -1,172 +1,174 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_loader_test.h" #include #include #include #include #include #include #include "KisDocument.h" #include "kis_image.h" #include "testutil.h" #include "KisPart.h" #include #include #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_time_range.h" +#include + void KisKraLoaderTest::initTestCase() { KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraLoaderTest::testLoading() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); KisImageSP image = doc->image(); image->lock(); QCOMPARE(image->nlayers(), 12); QCOMPARE(doc->documentInfo()->aboutInfo("title"), QString("test image for loading")); QCOMPARE(image->height(), 753); QCOMPARE(image->width(), 1000); QCOMPARE(image->colorSpace()->id(), KoColorSpaceRegistry::instance()->rgb8()->id()); KisNodeSP node = image->root()->firstChild(); QVERIFY(node); QCOMPARE(node->name(), QString("Background")); QVERIFY(node->inherits("KisPaintLayer")); node = node->nextSibling(); QVERIFY(node); QCOMPARE(node->name(), QString("Group 1")); QVERIFY(node->inherits("KisGroupLayer")); QCOMPARE((int) node->childCount(), 2); } void testObligeSingleChildImpl(bool transpDefaultPixel) { QString id = !transpDefaultPixel ? "single_layer_no_channel_flags_nontransp_def_pixel.kra" : "single_layer_no_channel_flags_transp_def_pixel.kra"; QString fileName = TestUtil::fetchDataFileLazy(id); QScopedPointer doc(KisPart::instance()->createDocument()); const bool result = doc->loadNativeFormat(fileName); QVERIFY(result); KisImageSP image = doc->image(); QVERIFY(image); QCOMPARE(image->nlayers(), 2); KisNodeSP root = image->root(); KisNodeSP child = root->firstChild(); QVERIFY(child); QCOMPARE(root->original(), root->projection()); if (transpDefaultPixel) { QCOMPARE(root->original(), child->projection()); } else { QVERIFY(root->original() != child->projection()); } } void KisKraLoaderTest::testObligeSingleChild() { testObligeSingleChildImpl(true); } void KisKraLoaderTest::testObligeSingleChildNonTranspPixel() { testObligeSingleChildImpl(false); } void KisKraLoaderTest::testLoadAnimated() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test_animation.kra"); KisImageSP image = doc->image(); KisNodeSP node1 = image->root()->firstChild(); KisNodeSP node2 = node1->nextSibling(); QVERIFY(node1->inherits("KisPaintLayer")); QVERIFY(node2->inherits("KisPaintLayer")); KisPaintLayerSP layer1 = qobject_cast(node1.data()); KisPaintLayerSP layer2 = qobject_cast(node2.data()); QVERIFY(layer1->isAnimated()); QVERIFY(!layer2->isAnimated()); KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel1); QCOMPARE(channel1->keyframeCount(), 3); QCOMPARE(image->animationInterface()->framerate(), 17); QCOMPARE(image->animationInterface()->fullClipRange(), KisTimeRange::fromTime(15, 45)); QCOMPARE(image->animationInterface()->currentTime(), 19); KisPaintDeviceSP dev = layer1->paintDevice(); const KoColorSpace *cs = dev->colorSpace(); KoColor transparent(Qt::transparent, cs); KoColor white(Qt::white, cs); KoColor red(Qt::red, cs); image->animationInterface()->switchCurrentTimeAsync(0); image->waitForDone(); QCOMPARE(dev->exactBounds(), QRect(506, 378, 198, 198)); QCOMPARE(dev->x(), -26); QCOMPARE(dev->y(), -128); QCOMPARE(dev->defaultPixel(), transparent); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(615, 416, 129, 129)); QCOMPARE(dev->x(), 502); QCOMPARE(dev->y(), 224); QCOMPARE(dev->defaultPixel(), white); image->animationInterface()->switchCurrentTimeAsync(30); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(729, 452, 45, 44)); QCOMPARE(dev->x(), 645); QCOMPARE(dev->y(), -10); QCOMPARE(dev->defaultPixel(), red); } -QTEST_MAIN(KisKraLoaderTest) +KISTEST_MAIN(KisKraLoaderTest) diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index 0ab3645cab..fdbeb2db9e 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,549 +1,550 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include +#include void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include #include "generator/kis_generator_registry.h" #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { TestUtil::ReferenceImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include "kis_shape_layer.h" #include #include void KisKraSaverTest::testRoundTripShapeLayer() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); KisShapeSelection *shapeSelection = new KisShapeSelection(p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } -QTEST_MAIN(KisKraSaverTest) +KISTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/png/tests/kis_png_test.cpp b/plugins/impex/png/tests/kis_png_test.cpp index e7b4f295b8..e7792c8b5b 100644 --- a/plugins/impex/png/tests/kis_png_test.cpp +++ b/plugins/impex/png/tests/kis_png_test.cpp @@ -1,39 +1,40 @@ /* * 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 - #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 void KisPngTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } -QTEST_MAIN(KisPngTest) + +KISTEST_MAIN(KisPngTest) diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/ppm/tests/kis_ppm_test.cpp index 8fcce610be..5feed43af1 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.cpp +++ b/plugins/impex/ppm/tests/kis_ppm_test.cpp @@ -1,38 +1,39 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ppm_test.h" #include #include -#include +#include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisPPMTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } -QTEST_MAIN(KisPPMTest) + +KISTEST_MAIN(KisPPMTest) diff --git a/plugins/impex/psd/tests/kis_psd_test.cpp b/plugins/impex/psd/tests/kis_psd_test.cpp index dac6954755..f309ef2125 100644 --- a/plugins/impex/psd/tests/kis_psd_test.cpp +++ b/plugins/impex/psd/tests/kis_psd_test.cpp @@ -1,351 +1,351 @@ /* * Copyright (C) 2009 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_psd_test.h" #include #include -#include +#include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #include #include "kis_group_layer.h" #include "kis_psd_layer_style.h" #include "kis_paint_device_debug_utils.h" void KisPSDTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } void KisPSDTest::testOpening() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); QScopedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); QCOMPARE(status, KisImportExportFilter::OK); Q_ASSERT(doc->image()); } QSharedPointer openPsdDocument(const QFileInfo &fileInfo) { QSharedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(fileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); return doc; } void KisPSDTest::testTransparencyMask() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/masks.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); doc->setFileBatchMode(true); doc->setMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_tmask.psd"); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); QVERIFY(doc->image()->root()->lastChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()->inherits("KisTransparencyMask")); } } void KisPSDTest::testOpenGrayscaleMultilayered() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/gray.psd"); //QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/100x100gray8.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); } void KisPSDTest::testOpenGroupLayers() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "group_layers.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisNodeSP node = TestUtil::findNode(doc->image()->root(), "Group 1 PT"); KisGroupLayer *group = dynamic_cast(node.data()); QVERIFY(group); QVERIFY(group->passThroughMode()); } void KisPSDTest::testOpenLayerStyles() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->dropShadow()); QVERIFY(layer->layerStyle()->dropShadow()->effectEnabled()); } void KisPSDTest::testOpenLayerStylesWithPattern() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); } void KisPSDTest::testOpenLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } void KisPSDTest::testSaveLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); doc->setFileBatchMode(true); const QByteArray mimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd"); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), mimeType); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); //QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single")); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } } void KisPSDTest::testOpeningFromOpenCanvas() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_krita_psd_from_opencanvas.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QVERIFY(doc->image()->root()->firstChild()); } void KisPSDTest::testOpeningAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); bool shouldFailTheTest = false; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "ml_cmyk_16b.psd") { //continue; } //dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { /** * 32bit images are expected to fail atm, their loading is not implemented */ if (!sourceFileInfo.fileName().contains("_32b")) { shouldFailTheTest = true; } errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } // just check visually if the file loads fine KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), sourceFileInfo.fileName(), "dd"); } QVERIFY(!shouldFailTheTest); } void KisPSDTest::testSavingAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "sl_rgb_8b.psd") { //continue; } dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } QString baseName = sourceFileInfo.fileName(); //QString originalName = QString("%1_0orig").arg(baseName); //QString resultName = QString("%1_1result").arg(baseName); QString tempPsdName = QString("%1_3interm.psd").arg(baseName); QImage refImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); // uncomment to do a visual check // KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), originalName, "dd"); doc->setFileBatchMode(true); doc->setMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + tempPsdName); dbgKrita << "Saving" << ppVar(dstFileInfo.fileName()); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); // uncomment to do a visual check //KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), resultName, "dd"); QImage resultImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); QCOMPARE(resultImage, refImage); } } } -QTEST_MAIN(KisPSDTest) +KISTEST_MAIN(KisPSDTest) diff --git a/plugins/impex/svg/tests/CMakeLists.txt b/plugins/impex/svg/tests/CMakeLists.txt index 9fedf4f2cf..82e0ea0717 100644 --- a/plugins/impex/svg/tests/CMakeLists.txt +++ b/plugins/impex/svg/tests/CMakeLists.txt @@ -1,10 +1,10 @@ -set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) -include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) - +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/sdk/tests) +include(ECMAddTests) macro_add_unittest_definitions() ecm_add_test( kis_svg_test.cpp - TEST_NAME KisSvgTest + TEST_NAME plugins-impex-KisSvgTest LINK_LIBRARIES kritaui Qt5::Test ) diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/svg/tests/kis_svg_test.cpp index 6b47e8aadf..0d146352be 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/svg/tests/kis_svg_test.cpp @@ -1,39 +1,40 @@ /* * 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_svg_test.h" #include #include -#include +#include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisSvgTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } -QTEST_MAIN(KisSvgTest) + +KISTEST_MAIN(KisSvgTest) diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index d4f623d447..4a8268d47c 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,120 +1,121 @@ /* * 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 #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.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 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); } 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); KisImportExportFilter::ConversionStatus 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 } -QTEST_MAIN(KisTiffTest) +KISTEST_MAIN(KisTiffTest) diff --git a/plugins/impex/xcf/tests/kis_xcf_test.cpp b/plugins/impex/xcf/tests/kis_xcf_test.cpp index 13ff4d6854..25c1c90c2a 100644 --- a/plugins/impex/xcf/tests/kis_xcf_test.cpp +++ b/plugins/impex/xcf/tests/kis_xcf_test.cpp @@ -1,38 +1,38 @@ /* * 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_xcf_test.h" #include #include -#include +#include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisXCFTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } -QTEST_MAIN(KisXCFTest) +KISTEST_MAIN(KisXCFTest) diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 5bbaec35b6..9b742da8ed 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1278 +1,1278 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "krita_container_utils.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_changesTracker(&m_transaction) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged()), this, SLOT(slotTrackerChangedConfig())); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->transformArgs(); initGuiAfterTransformMode(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode, &oldTransformedNodes) && args->mode() == mode && oldRootNode == currentNode) { KisNodeList perspectiveTransformedNodes = fetchNodesList(mode, currentNode, m_workRecursively); if (KritaUtils::compareListsUnordered(oldTransformedNodes, perspectiveTransformedNodes)) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); forceRepaintDelayedLayers(oldRootNode); result = true; } } return result; } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; m_changesTracker.requestUndo(); } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) { return; } /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (image()->tryBarrierLock()) { image()->unlock(); } else { return; } /** * We must ensure that the currently selected subtree * has finished all its updates. */ forceRepaintDelayedLayers(currentNode); ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); if (nodesList.isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selected layer cannot be transformed with active transformation mode "), koIcon("object-locked"), 4000, KisFloatingMessage::High); // force-reset transform mode to default initTransformMode(mode); return; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, nodesList, resources->activeSelection(), image().data()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); - QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); + const QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); if (!selection && resources->activeSelection()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); } if (srcRect.isEmpty()) { delete strategy; KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); // force-reset transform mode to default initTransformMode(mode); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); initThumbnailImage(previewDevice); updateSelectionPath(); if (!forceReset && fetchedFromCommand) { m_currentArgs = fetchedArgs; initGuiAfterTransformMode(); } else if (forceReset || !tryInitTransformModeFromNode(currentNode)) { initTransformMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); bool haveInvisibleNodes = clearDevices(nodesList); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformClearedDevices(); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); // root node is used for progress only image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; m_changesTracker.commitConfig(m_currentArgs); } void KisToolTransform::slotTrackerChangedConfig() { slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool KisToolTransform::clearDevices(const QList &nodes) { bool haveInvisibleNodes = false; Q_FOREACH (KisNodeSP node, nodes) { haveInvisibleNodes |= !node->visible(false); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); } return haveInvisibleNodes; } void KisToolTransform::transformClearedDevices() { Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(node); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, node)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) { KIS_SAFE_ASSERT_RECOVER_RETURN(root); KisLayerUtils::forceAllDelayedNodesUpdate(root); image()->waitForDone(); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index 8bab4d564d..079555d6fa 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,320 +1,372 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" +#include "kis_sequential_iterator.h" +#include "kis_selection_mask.h" +#include "kis_image_config.h" TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode, KisNodeList processedNodes, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), m_selection(selection) { if (rootNode->childCount() || !rootNode->paintDevice()) { KisPaintDeviceSP device; if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { device = tmask->buildPreviewDevice(); /** * When working with transform mask, selections are not * taken into account. */ m_selection = 0; } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); device = rootNode->projection(); } m_previewDevice = createDeviceCache(device); } else { - m_previewDevice = createDeviceCache(rootNode->paintDevice()); - putDeviceCache(rootNode->paintDevice(), m_previewDevice); + KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); + + if (dynamic_cast(rootNode.data())) { + KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && + cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { + + cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); + } + + m_previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + const QRect srcRect = cacheDevice->exactBounds(); + + KisSequentialConstIterator srcIt(cacheDevice, srcRect); + KisSequentialIterator dstIt(m_previewDevice, srcRect); + + const int pixelSize = m_previewDevice->colorSpace()->pixelSize(); + + + KisImageConfig cfg(true); + KoColor pixel(cfg.selectionOverlayMaskColor(), m_previewDevice->colorSpace()); + + const qreal coeff = 1.0 / 255.0; + const qreal baseOpacity = 0.5; + + while (srcIt.nextPixel() && dstIt.nextPixel()) { + qreal gray = srcIt.rawDataConst()[0]; + qreal alpha = srcIt.rawDataConst()[1]; + + pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); + memcpy(dstIt.rawData(), pixel.data(), pixelSize); + } + + } else { + m_previewDevice = cacheDevice; + } + + putDeviceCache(rootNode->paintDevice(), cacheDevice); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_previewDevice); m_savedRootNode = rootNode; m_savedProcessedNodes = processedNodes; } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::previewDevice() const { return m_previewDevice; } KisSelectionSP TransformStrokeStrategy::realSelection() const { return m_selection; } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); if(td) { m_savedTransformArgs = td->config; if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); + + /** + * Seleciton masks might have an overlay enabled, we should disable that + */ + if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { + KisSelectionSP selection = mask->selection(); + if (selection) { + selection->setVisible(false); + m_deactivatedSelections.append(selection); + mask->setDirty(); + } + } + } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = m_savedTransformArgs; data->rootNode = m_savedRootNode; data->transformedNodes = m_savedProcessedNodes; command->setExtraData(data); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); + m_deactivatedSelections.append(m_selection); } } void TransformStrokeStrategy::finishStrokeCallback() { - if (m_selection) { - m_selection->setVisible(true); + Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { + selection->setVisible(true); } KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void TransformStrokeStrategy::cancelStrokeCallback() { KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); - if (m_selection) { - m_selection->setVisible(true); + Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { + selection->setVisible(true); } } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h index 97b1c1c78a..a7d8171fb8 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h @@ -1,127 +1,128 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TRANSFORM_STROKE_STRATEGY_H #define __TRANSFORM_STROKE_STRATEGY_H #include #include #include #include #include "tool_transform_args.h" #include #include class KisPostExecutionUndoAdapter; class TransformStrokeStrategy : public KisStrokeStrategyUndoCommandBased { public: class TransformData : public KisStrokeJobData { public: enum Destination { PAINT_DEVICE, SELECTION, }; public: TransformData(Destination _destination, const ToolTransformArgs &_config, KisNodeSP _node) : KisStrokeJobData(CONCURRENT, NORMAL), destination(_destination), config(_config), node(_node) { } Destination destination; ToolTransformArgs config; KisNodeSP node; }; class ClearSelectionData : public KisStrokeJobData { public: ClearSelectionData(KisNodeSP _node) : KisStrokeJobData(SEQUENTIAL, NORMAL), node(_node) { } KisNodeSP node; }; public: TransformStrokeStrategy(KisNodeSP rootNode, KisNodeList processedNodes, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade); ~TransformStrokeStrategy() override; KisPaintDeviceSP previewDevice() const; KisSelectionSP realSelection() const; void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; static bool fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes); protected: void postProcessToplevelCommand(KUndo2Command *command) override; private: KoUpdaterPtr fetchUpdater(KisNodeSP node); void transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper); void clearSelection(KisPaintDeviceSP device); //void transformDevice(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); bool checkBelongsToSelection(KisPaintDeviceSP device) const; KisPaintDeviceSP createDeviceCache(KisPaintDeviceSP src); bool haveDeviceInCache(KisPaintDeviceSP src); void putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache); KisPaintDeviceSP getDeviceCache(KisPaintDeviceSP src); private: KisSelectionSP m_selection; QMutex m_devicesCacheMutex; QHash m_devicesCacheHash; KisPaintDeviceSP m_previewDevice; KisTransformMaskSP writeToTransformMask; ToolTransformArgs m_savedTransformArgs; KisNodeSP m_savedRootNode; KisNodeList m_savedProcessedNodes; + QList m_deactivatedSelections; }; #endif /* __TRANSFORM_STROKE_STRATEGY_H */ diff --git a/sdk/tests/kistest.h b/sdk/tests/kistest.h new file mode 100644 index 0000000000..ef912e92d5 --- /dev/null +++ b/sdk/tests/kistest.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Boudewijn Rempt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISTEST +#define KISTEST + +#include +#include +#include +#include +#include + +#ifndef QT_NO_OPENGL +# define QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS \ + extern Q_TESTLIB_EXPORT std::set *(*qgpu_features_ptr)(const QString &); \ + extern Q_GUI_EXPORT std::set *qgpu_features(const QString &); +# define QTEST_ADD_GPU_BLACKLIST_SUPPORT \ + qgpu_features_ptr = qgpu_features; +#else +# define QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS +# define QTEST_ADD_GPU_BLACKLIST_SUPPORT +#endif + +#if defined(QT_NETWORK_LIB) +# include +#endif +#include + +#ifdef QT_KEYPAD_NAVIGATION +# define QTEST_DISABLE_KEYPAD_NAVIGATION QApplication::setNavigationMode(Qt::NavigationModeNone); +#else +# define QTEST_DISABLE_KEYPAD_NAVIGATION +#endif + +#define KISTEST_MAIN(TestObject) \ +QT_BEGIN_NAMESPACE \ +QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS \ +QT_END_NAMESPACE \ +int main(int argc, char *argv[]) \ +{ \ + qputenv("EXTRA_RESOURCE_DIRS", QByteArray(KRITA_EXTRA_RESOURCE_DIRS)); \ + QApplication app(argc, argv); \ + app.setAttribute(Qt::AA_Use96Dpi, true); \ + QTEST_DISABLE_KEYPAD_NAVIGATION \ + QTEST_ADD_GPU_BLACKLIST_SUPPORT \ + TestObject tc; \ + QTEST_SET_MAIN_SOURCE_PATH \ + return QTest::qExec(&tc, argc, argv); \ +} + + + + + +#endif diff --git a/sdk/tests/qimage_based_test.h b/sdk/tests/qimage_based_test.h index ad666d2175..ab021ba621 100644 --- a/sdk/tests/qimage_based_test.h +++ b/sdk/tests/qimage_based_test.h @@ -1,316 +1,329 @@ /* * 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. */ #ifndef __QIMAGE_BASED_TEST_H #define __QIMAGE_BASED_TEST_H +#ifndef USE_DOCUMENT +#define USE_DOCUMENT 1 +#endif /* USE_DOCUMENT */ + #include "testutil.h" #include #include #include #include #include + +#if USE_DOCUMENT #include "KisDocument.h" #include "kis_shape_layer.h" +#else +#include "kis_filter_configuration.h" +#endif /* USE_DOCUMENT */ #include "kis_undo_stores.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_adjustment_layer.h" #include "kis_transparency_mask.h" #include "kis_clone_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_registry.h" #include "commands/kis_selection_commands.h" namespace TestUtil { class QImageBasedTest { public: QImageBasedTest(const QString &directoryName) : m_directoryName(directoryName) { } // you need to declare your own test function // See KisProcessingTest for example protected: /** * Creates a complex image connected to a surrogate undo store */ KisImageSP createImage(KisSurrogateUndoStore *undoStore) { QImage sourceImage(fetchDataFileLazy("hakonepa.png")); QRect imageRect = QRect(QPoint(0,0), sourceImage.size()); QRect transpRect(50,50,300,300); QRect blurRect(66,66,300,300); QPoint blurShift(34,34); QPoint cloneShift(75,75); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisAdjustmentLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); blur1->internalSelection()->clear(); blur1->internalSelection()->pixelSelection()->select(blurRect); blur1->setX(blurShift.x()); blur1->setY(blurShift.y()); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); paintLayer1->paintDevice()->convertFromQImage(sourceImage, 0, 0, 0); KisCloneLayerSP cloneLayer1 = new KisCloneLayer(paintLayer1, image, "clone1", OPACITY_OPAQUE_U8); cloneLayer1->setX(cloneShift.x()); cloneLayer1->setY(cloneShift.y()); image->addNode(cloneLayer1); image->addNode(blur1); image->addNode(paintLayer1); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("tmask1"); transparencyMask1->testingInitSelection(transpRect, paintLayer1); image->addNode(transparencyMask1, paintLayer1); return image; } /** * Creates a simple image with one empty layer and connects it to * a surrogate undo store */ KisImageSP createTrivialImage(KisSurrogateUndoStore *undoStore) { QRect imageRect = QRect(0, 0, 640, 441); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1); return image; } void addGlobalSelection(KisImageSP image) { QRect selectionRect(40,40,300,300); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(0, image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(selectionRect); KUndo2Command *cmd = new KisSetGlobalSelectionCommand(image, selection); image->undoAdapter()->addCommand(cmd); } +#if USE_DOCUMENT + void addShapeLayer(KisDocument *doc, KisImageSP image) { KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image.data(), "shape", OPACITY_OPAQUE_U8); image->addNode(shapeLayer); KoShapeFactoryBase *f1 = KoShapeRegistry::instance()->get("StarShape"); KoShapeFactoryBase *f2 = KoShapeRegistry::instance()->get("RectangleShape"); KoShape *shape1 = f1->createDefaultShape(); KoShape *shape2 = f2->createDefaultShape(); shape1->setPosition(QPointF(100,100)); shape2->setPosition(QPointF(200,200)); shapeLayer->addShape(shape1); shapeLayer->addShape(shape2); QApplication::processEvents(); } +#endif /* USE_DOCUMENT*/ + bool checkLayersInitial(KisImageWSP image, int baseFuzzyness = 0) { QString prefix = "initial_with_selection"; QString prefix2 = findNode(image->root(), "shape") ? "_with_shape" : ""; return checkLayers(image, prefix + prefix2, baseFuzzyness); } bool checkLayersInitialRootOnly(KisImageWSP image, int baseFuzzyness = 0) { QString prefix = "initial_with_selection"; QString prefix2 = findNode(image->root(), "shape") ? "_with_shape" : ""; return checkLayers(image, prefix + prefix2, baseFuzzyness, false); } /** * Checks the content of image's layers against the set of * QImages stored in @p prefix subfolder */ bool checkLayers(KisImageWSP image, const QString &prefix, int baseFuzzyness = 0, bool recursive = true) { QVector images; QVector names; fillNamesImages(image->root(), image->bounds(), images, names, recursive); bool valid = true; const int stackSize = images.size(); for(int i = 0; i < stackSize; i++) { if(!checkOneQImage(images[i], prefix, names[i], baseFuzzyness)) { valid = false; } } return valid; } /** * Checks the content of one image's layer against the QImage * stored in @p prefix subfolder */ bool checkOneLayer(KisImageWSP image, KisNodeSP node, const QString &prefix, int baseFuzzyness = 0) { QVector images; QVector names; fillNamesImages(node, image->bounds(), images, names); return checkOneQImage(images.first(), prefix, names.first(), baseFuzzyness); } // add default bounds param bool checkOneDevice(KisPaintDeviceSP device, const QString &prefix, const QString &name, int baseFuzzyness = 0) { QImage image = device->convertToQImage(0); return checkOneQImage(image, prefix, name, baseFuzzyness); } KisNodeSP findNode(KisNodeSP root, const QString &name) { return TestUtil::findNode(root, name); } private: bool checkOneQImage(const QImage &image, const QString &prefix, const QString &name, int baseFuzzyness) { QString realName = prefix + "_" + name + ".png"; QString expectedName = prefix + "_" + name + "_expected.png"; bool valid = true; QString fullPath = fetchDataFileLazy(m_directoryName + QDir::separator() + prefix + QDir::separator() + realName); if (fullPath.isEmpty()) { // Try without the testname subdirectory fullPath = fetchDataFileLazy(prefix + QDir::separator() + realName); } if (fullPath.isEmpty()) { // Try without the prefix subdirectory fullPath = fetchDataFileLazy(m_directoryName + QDir::separator() + realName); } QImage ref(fullPath); QPoint temp; int fuzzy = baseFuzzyness; { QStringList terms = name.split('_'); if(terms[0] == "root" || terms[0] == "blur1" || terms[0] == "shape") { fuzzy++; } } if(ref != image && !TestUtil::compareQImages(temp, ref, image, fuzzy)) { dbgKrita << "--- Wrong image:" << realName; valid = false; image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + realName); ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + expectedName); } return valid; } void fillNamesImages(KisNodeSP node, const QRect &rc, QVector &images, QVector &names, bool recursive = true) { while (node) { if(node->paintDevice()) { names.append(node->name() + "_paintDevice"); images.append(node->paintDevice()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if(node->original() && node->original() != node->paintDevice()) { names.append(node->name() + "_original"); images.append(node->original()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if(node->projection() && node->projection() != node->paintDevice()) { names.append(node->name() + "_projection"); images.append(node->projection()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if (recursive) { fillNamesImages(node->firstChild(), rc, images, names); } node = node->nextSibling(); } } private: QString m_directoryName; }; } #endif /* __QIMAGE_BASED_TEST_H */ diff --git a/sdk/tests/testutil.h b/sdk/tests/testutil.h index 0cc62921f8..1404e84ee8 100644 --- a/sdk/tests/testutil.h +++ b/sdk/tests/testutil.h @@ -1,527 +1,529 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TEST_UTIL #define TEST_UTIL #include #include #include #include +#include #include #include #include #include #include #include #include #include "kis_node_graph_listener.h" #include "kis_iterator_ng.h" #include "kis_image.h" #include "testing_nodes.h" +#include "kistest.h" #ifndef FILES_DATA_DIR #define FILES_DATA_DIR "." #endif #ifndef FILES_DEFAULT_DATA_DIR #define FILES_DEFAULT_DATA_DIR "." #endif #include "qimage_test_util.h" #define KIS_COMPARE_RF(expr, ref) \ if ((expr) != (ref)) { \ qDebug() << "Compared values are not the same at line" << __LINE__; \ qDebug() << " Actual : " << #expr << "=" << (expr); \ qDebug() << " Expected: " << #ref << "=" << (ref); \ return false; \ } /** * Routines that are useful for writing efficient tests */ namespace TestUtil { inline KisNodeSP findNode(KisNodeSP root, const QString &name) { if(root->name() == name) return root; KisNodeSP child = root->firstChild(); while (child) { if((root = findNode(child, name))) return root; child = child->nextSibling(); } return KisNodeSP(); } inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t")) { qDebug() << node->name(); KisNodeSP child = node->firstChild(); while (child) { if (child->childCount() > 0) { dumpNodeStack(child, prefix + "\t"); } else { qDebug() << prefix << child->name(); } child = child->nextSibling(); } } class TestProgressBar : public KoProgressProxy { public: TestProgressBar() : m_min(0), m_max(0), m_value(0) {} int maximum() const override { return m_max; } void setValue(int value) override { m_value = value; } void setRange(int min, int max) override { m_min = min; m_max = max; } void setFormat(const QString &format) override { m_format = format; } void setAutoNestedName(const QString &name) { m_autoNestedName = name; KoProgressProxy::setAutoNestedName(name); } int min() { return m_min; } int max() { return m_max; } int value() { return m_value; } QString format() { return m_format; } QString autoNestedName() { return m_autoNestedName; } private: int m_min; int m_max; int m_value; QString m_format; QString m_autoNestedName; }; inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2) { // QTime t; // t.start(); QRect rc1 = dev1->exactBounds(); QRect rc2 = dev2->exactBounds(); if (rc1 != rc2) { pt.setX(-1); pt.setY(-1); } KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width()); KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width()); int pixelSize = dev1->pixelSize(); for (int y = 0; y < rc1.height(); ++y) { do { if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) return false; } while (iter1->nextPixel() && iter2->nextPixel()); iter1->nextRow(); iter2->nextRow(); } // qDebug() << "comparePaintDevices time elapsed:" << t.elapsed(); return true; } template inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0) { QRect rc1 = dev1->exactBounds(); QRect rc2 = dev2->exactBounds(); if (rc1 != rc2) { qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2); return false; } KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width()); KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width()); int pixelSize = dev1->pixelSize(); for (int y = 0; y < rc1.height(); ++y) { do { if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) { const channel_type* p1 = reinterpret_cast(iter1->oldRawData()); const channel_type* p2 = reinterpret_cast(iter2->oldRawData()); if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue; qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y(); qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3]; qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3]; return false; } } while (iter1->nextPixel() && iter2->nextPixel()); iter1->nextRow(); iter2->nextRow(); } return true; } #ifdef FILES_OUTPUT_DIR struct ReferenceImageChecker { enum StorageType { InternalStorage = 0, ExternalStorage }; ReferenceImageChecker(const QString &prefix, const QString &testName, StorageType storageType = ExternalStorage) : m_storageType(storageType), m_prefix(prefix), m_testName(testName), m_success(true), m_maxFailingPixels(100), m_fuzzy(1) { } void setMaxFailingPixels(int value) { m_maxFailingPixels = value; } void setFuzzy(int fuzzy){ m_fuzzy = fuzzy; } bool testPassed() const { return m_success; } inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) { bool result = false; if (m_storageType == ExternalStorage) { result = checkQImageExternal(device->convertToQImage(0, image->bounds()), m_testName, m_prefix, caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels); } else { result = checkQImage(device->convertToQImage(0, image->bounds()), m_testName, m_prefix, caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels); } m_success &= result; return result; } inline bool checkImage(KisImageSP image, const QString &testName) { bool result = checkDevice(image->projection(), image, testName); m_success &= result; return result; } private: bool m_storageType; QString m_prefix; QString m_testName; bool m_success; int m_maxFailingPixels; int m_fuzzy; }; #endif inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y) { KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->oldRawData(); return *pix; } inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s) { KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1); quint8 *pix = iter->rawData(); *pix = s; } inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected) { KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); for (int y = rc.y(); y < rc.y() + rc.height(); y++) { for (int x = rc.x(); x < rc.x() + rc.width(); x++) { if(*((quint8*)it->rawData()) != expected) { errKrita << "At point:" << x << y; errKrita << "Expected pixel:" << expected; errKrita << "Actual pixel: " << *((quint8*)it->rawData()); return false; } it->nextPixel(); } it->nextRow(); } return true; } class TestNode : public DefaultNode { Q_OBJECT public: KisNodeSP clone() const override { return KisNodeSP(new TestNode(*this)); } }; class TestGraphListener : public KisNodeGraphListener { public: void aboutToAddANode(KisNode *parent, int index) override { KisNodeGraphListener::aboutToAddANode(parent, index); beforeInsertRow = true; } void nodeHasBeenAdded(KisNode *parent, int index) override { KisNodeGraphListener::nodeHasBeenAdded(parent, index); afterInsertRow = true; } void aboutToRemoveANode(KisNode *parent, int index) override { KisNodeGraphListener::aboutToRemoveANode(parent, index); beforeRemoveRow = true; } void nodeHasBeenRemoved(KisNode *parent, int index) override { KisNodeGraphListener::nodeHasBeenRemoved(parent, index); afterRemoveRow = true; } void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override { KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex); beforeMove = true; } void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override { KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex); afterMove = true; } bool beforeInsertRow; bool afterInsertRow; bool beforeRemoveRow; bool afterRemoveRow; bool beforeMove; bool afterMove; void resetBools() { beforeRemoveRow = false; afterRemoveRow = false; beforeInsertRow = false; afterInsertRow = false; beforeMove = false; afterMove = false; } }; } #include #include #include #include "kis_undo_stores.h" #include "kis_layer_utils.h" namespace TestUtil { struct MaskParent { MaskParent(const QRect &_imageRect = QRect(0,0,512,512)) : imageRect(_imageRect) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); undoStore = new KisSurrogateUndoStore(); image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image"); layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8)); image->addNode(KisNodeSP(layer.data())); } void waitForImageAndShapeLayers() { qApp->processEvents(); image->waitForDone(); KisLayerUtils::forceAllDelayedNodesUpdate(image->root()); qApp->processEvents(); image->waitForDone(); } KisSurrogateUndoStore *undoStore; const QRect imageRect; KisImageSP image; KisPaintLayerSP layer; }; } namespace TestUtil { class MeasureAvgPortion { public: MeasureAvgPortion(int period) : m_period(period), m_val(0), m_total(0), m_cycles(0) { } ~MeasureAvgPortion() { printValues(true); } void addVal(int x) { m_val += x; } void addTotal(int x) { m_total += x; m_cycles++; printValues(); } private: void printValues(bool force = false) { if (m_cycles > m_period || force) { qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total); qDebug() << "Avg. Val: " << qreal(m_val) / m_cycles; qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles; qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles); m_val = 0; m_total = 0; m_cycles = 0; } } private: int m_period; qint64 m_val; qint64 m_total; qint64 m_cycles; }; struct MeasureDistributionStats { MeasureDistributionStats(int numBins, const QString &name = QString()) : m_numBins(numBins), m_name(name) { reset(); } void reset() { m_values.clear(); m_values.resize(m_numBins); } void addValue(int value) { addValue(value, 1); } void addValue(int value, int increment) { KIS_SAFE_ASSERT_RECOVER_RETURN(value >= 0); if (value >= m_numBins) { m_values[m_numBins - 1] += increment; } else { m_values[value] += increment; } } void print() { qCritical() << "============= Stats =============="; if (!m_name.isEmpty()) { qCritical() << "Name:" << m_name; } int total = 0; for (int i = 0; i < m_numBins; i++) { total += m_values[i]; } for (int i = 0; i < m_numBins; i++) { if (!m_values[i]) continue; const QString lastMarker = i == m_numBins - 1 ? "> " : " "; const QString line = QString(" %1%2: %3 (%4%)") .arg(lastMarker) .arg(i, 3) .arg(m_values[i], 5) .arg(qreal(m_values[i]) / total * 100.0, 7, 'g', 2); qCritical() << qPrintable(line); } qCritical() << "---- ----"; qCritical() << qPrintable(QString("Total: %1").arg(total)); qCritical() << "=================================="; } private: QVector m_values; int m_numBins = 0; QString m_name; }; QStringList getHierarchy(KisNodeSP root, const QString &prefix = ""); bool checkHierarchy(KisNodeSP root, const QStringList &expected); } #endif