diff --git a/3rdparty/ext_png/CMakeLists.txt b/3rdparty/ext_png/CMakeLists.txt index b5ac62d0f6..3af51c3677 100755 --- a/3rdparty/ext_png/CMakeLists.txt +++ b/3rdparty/ext_png/CMakeLists.txt @@ -1,12 +1,12 @@ SET(PREFIX_ext_png "${EXTPREFIX}" ) ExternalProject_Add( ext_png DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/libpng-1.6.23.tar.gz URL_MD5 3a8fb380b0fb39cb69efe47901917e38 INSTALL_DIR ${PREFIX_ext_png} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_png} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_png} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DPNG_TESTS=OFF UPDATE_COMMAND "" DEPENDS ext_zlib ) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0f80c43fa..02cc6c8126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,663 +1,665 @@ 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) option(OVERRIDE_QT_VERSION "Use this to make it possible to build with Qt < 5.6.0. There will be bugs." OFF) if (OVERRIDE_QT_VERSION) set(MIN_QT_VERSION 5.4.0) endif() set(MIN_FRAMEWORKS_VERSION 5.7.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 (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.9 -Wno-macro-redefined -Wno-deprecated-register) endif() -if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) - add_definitions(-Werror=delete-incomplete) +if (LINUX) + if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WINDOWS) + add_definitions(-Werror=delete-incomplete) + endif() 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.0.0-pre-alpha") set(KRITA_STABLE_VERSION_MAJOR 4) # 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MINOR 0) # 0 for 3.0, 1 for 3.1, etc. set(KRITA_VERSION_RELEASE 0) # 88 for pre-alpha, 89 for Alpha, increase for next test releases, set 0 for first Stable, etc. 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 2017) # 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_revision(GIT_REFSPEC GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1 AND GIT_BRANCH) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) endif() if(NOT DEFINED RELEASE_BUILD) # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) if (INDEX EQUAL -1) set(RELEASE_BUILD FALSE) else() set(RELEASE_BUILD TRUE) endif() endif() message(STATUS "Release build: ${RELEASE_BUILD}") # 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(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).") include(MacroJPEG) ########################################################### ## Look for Python3. It is also searched by KF5, ## ## so we should request the correct version in advance ## ########################################################### find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.19 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 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_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_NO_URL_CAST_FROM_STRING -DQT_DISABLE_DEPRECATED_BEFORE=0 ) 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() 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() # enable exceptions globally kde_enable_exceptions() # only with this definition will all the FOO_TEST_EXPORT macro do something # TODO: check if this can be moved to only those places which make use of it, # to reduce global compiler definitions that would trigger a recompile of # everything on a change (like adding/removing tests to/from the build) if(BUILD_TESTING) add_definitions(-DCOMPILING_TESTS) endif() 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 REQUIRED COMPONENTS system) # for pigment and stage include_directories(${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 ## ############################ ########################### ## ## Check for OpenEXR ## 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") 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) ## ## 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 REQUIRED "3.0") set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## set(EXIV2_MIN_VERSION "0.16") find_package(Exiv2 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 REQUIRED "2.4") 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 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() 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 -fPIC") elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC") 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}) if(WIN32) set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} LIBRARY ${INSTALL_TARGETS_DEFAULT_ARGS} ARCHIVE ${INSTALL_TARGETS_DEFAULT_ARGS} ) endif() ## ## Test endianess ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for pthreads (for G'Mic) ## find_package(Threads) set_package_properties(Threads PROPERTIES DESCRIPTION "PThreads - A low-level threading library" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic plugin") ## ## Test for OpenMP (for G'Mic) ## find_package(OpenMP) set_package_properties(OpenMP PROPERTIES DESCRIPTION "A low-level parallel execution library" URL "http://openmp.org/wp/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic plugin") ## ## Test for Curl (for G'Mic) ## find_package(CURL) set_package_properties(CURL PROPERTIES DESCRIPTION "A tool to fetch remote data" URL "http://curl.haxx.se/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic plugin") ############################ ############################# ## 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) message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/krita/krita.xmlgui b/krita/krita.xmlgui index a05742d291..4ea7daad9a 100644 --- a/krita/krita.xmlgui +++ b/krita/krita.xmlgui @@ -1,376 +1,377 @@ &File &Edit &View &Canvas - - + + &Snap To - - - - - - - - + + + + + + + + &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform - - - - - &Rotate - - - - - - + + + + + &Rotate + + + + + + - - + + S&plit - - S&plit Alpha - - - - - + + S&plit Alpha + + + + + &Select Filte&r &Tools Recording Macros Setti&ngs &Help + File Brushes and Stuff diff --git a/libs/flake/KoFilterEffect.h b/libs/flake/KoFilterEffect.h index f0173f79c5..2817f2861c 100644 --- a/libs/flake/KoFilterEffect.h +++ b/libs/flake/KoFilterEffect.h @@ -1,170 +1,170 @@ /* This file is part of the KDE project * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2009 Jan Hambrecht * * 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 * Library 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 _KO_FILTER_EFFECT_H_ #define _KO_FILTER_EFFECT_H_ class QImage; class QString; class QRectF; class KoXmlWriter; class KoFilterEffectRenderContext; class KoFilterEffectLoadingContext; -class KoXmlElement; +#include #include "kritaflake_export.h" #include /** * This is the base for filter effect (blur, invert...) that can be applied on a shape. * All sizes and coordinates of the filter effect are stored in object bounding box * coordinates, where (0,0) refers to the top-left corner of a shapes bounding rect * and (1,1) refers to the bottom-right corner. * When loading, a transformation matrix is given to convert from user space coordinates. * Another transformation matrix is given via the render context to convert back to * user space coordinates when applying the effect. * Using object bounding box coordinates internally makes it easy to share effects * between shapes or even between users via the filter effect resources. */ class KRITAFLAKE_EXPORT KoFilterEffect { public: KoFilterEffect(const QString &id, const QString &name); virtual ~KoFilterEffect(); /// Returns the user visible name of the filter QString name() const; /// Returns the unique id of the filter QString id() const; /// Sets the region the filter is applied to in bounding box units void setFilterRect(const QRectF &filterRect); /// Returns the region this filter is applied to in bounding box units QRectF filterRect() const; /// Returns the region this filter is applied to for the given bounding rect QRectF filterRectForBoundingRect(const QRectF &boundingRect) const; /** * Sets the name of the output image * * The name is used so that other effects can reference * the output of this effect as one of their input images. * * @param output the output image name */ void setOutput(const QString &output); /// Returns the name of the output image QString output() const; /** * Returns list of named input images of this filter effect. * * These names identify the input images for this filter effect. * These can be one of the keywords SourceGraphic, SourceAlpha, * BackgroundImage, BackgroundAlpha, FillPaint or StrokePaint, * as well as a named output of another filter effect in the stack. * An empty input list of the first effect in the stack default to * SourceGraphic, whereas on subsequent effects it defaults to the * result of the previous filter effect. */ QList inputs() const; /// Adds a new input at the end of the input list void addInput(const QString &input); /// Inserts an input at the giben position in the input list void insertInput(int index, const QString &input); /// Sets an existing input to a new value void setInput(int index, const QString &input); /// Removes an input from the given position in the input list void removeInput(int index); /** * Return the required number of input images. * The default required number of input images is 1. * Derived classes should call setRequiredInputCount to set * a different number. */ int requiredInputCount() const; /** * Returns the maximal number of input images. * The default maximal number of input images is 1. * Derived classes should call setMaximalInputCount to set * a different number. */ int maximalInputCount() const; /** * Apply the effect on an image. * @param image the image the filter should be applied to * @param context the render context providing additional data */ virtual QImage processImage(const QImage &image, const KoFilterEffectRenderContext &context) const = 0; /** * Apply the effect on a list of images. * @param images the images the filter should be applied to * @param context the render context providing additional data */ virtual QImage processImages(const QList &images, const KoFilterEffectRenderContext &context) const; /** * Loads data from given xml element. * @param element the xml element to load data from * @param context the loading context providing additional data * @return true if loading was successful, else false */ virtual bool load(const KoXmlElement &element, const KoFilterEffectLoadingContext &context) = 0; /** * Writes custom data to given xml element. * @param writer the xml writer to write data to */ virtual void save(KoXmlWriter &writer) = 0; protected: /// Sets the required number of input images void setRequiredInputCount(int count); /// Sets the maximal number of input images void setMaximalInputCount(int count); /** * Saves common filter attributes * * Saves result, subregion and input attributes. The input attrinbute * is only saved if required, maximal and actual input count equals 1. * All other filters have to write inputs on their own. */ void saveCommonAttributes(KoXmlWriter &writer); private: class Private; Private* const d; }; #endif // _KO_FILTER_EFFECT_H_ diff --git a/libs/flake/KoFilterEffectRegistry.h b/libs/flake/KoFilterEffectRegistry.h index cd1cbb3643..594d3d2116 100644 --- a/libs/flake/KoFilterEffectRegistry.h +++ b/libs/flake/KoFilterEffectRegistry.h @@ -1,61 +1,61 @@ /* 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 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 * Library 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 KOFILTEREFFECTREGISTRY_H #define KOFILTEREFFECTREGISTRY_H #include #include #include "kritaflake_export.h" -class KoXmlElement; +#include class KoFilterEffectLoadingContext; class KoFilterEffect; class KRITAFLAKE_EXPORT KoFilterEffectRegistry : public KoGenericRegistry { public: KoFilterEffectRegistry(); ~KoFilterEffectRegistry() override; /** * Return the only instance of KoFilterEffectRegistry. * Creates an instance on the first call. */ static KoFilterEffectRegistry *instance(); /** * Creates filter effect from given xml element. * @param element the xml element to load form * @return the created filter effect if successful, otherwise returns 0 */ KoFilterEffect *createFilterEffectFromXml(const KoXmlElement &element, const KoFilterEffectLoadingContext &context); private: KoFilterEffectRegistry(const KoFilterEffectRegistry&); KoFilterEffectRegistry operator=(const KoFilterEffectRegistry&); void init(); class Private; Private * const d; }; #endif // KOFILTEREFFECTREGISTRY_H diff --git a/libs/flake/KoFrameShape.h b/libs/flake/KoFrameShape.h index 062bfd2ffd..466a85e1e4 100644 --- a/libs/flake/KoFrameShape.h +++ b/libs/flake/KoFrameShape.h @@ -1,94 +1,94 @@ /* This file is part of the KDE project Copyright (C) 2008 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 KOFRAMESHAPE_H #define KOFRAMESHAPE_H #include "kritaflake_export.h" class KoShapeLoadingContext; -class KoXmlElement; +#include class QString; /** * @brief Base class for shapes that are saved as a part of a draw:frame. * * Shapes like the TextShape or the PictureShape are implementing this * class to deal with frames and their attributes. * * What follows is a sample taken out of an ODT-file that shows how this works * together; * @code * * * * @endcode * * The loading code of the shape gets passed the draw:frame element. Out of this element the * odf attributes can be loaded. Then it calls loadOdfFrame which loads the correct frame element * the object supports. The loading of the frame element is done in the loadOdfFrameElement. * * @code * bool PictureShape::loadOdf( const KoXmlElement & element, KoShapeLoadingContext &context ) * { * loadOdfAttributes( element, context, OdfAllAttributes ); * return loadOdfFrame( element, context ); * } * @endcode */ class KRITAFLAKE_EXPORT KoFrameShape { public: /** * Constructor. * * \param ns The namespace. E.g. KoXmlNS::draw * \param element The tag-name. E.g. "image" */ KoFrameShape(const QString &ns, const QString &tag); /** * Destructor. */ virtual ~KoFrameShape(); /** * Loads the content of the draw:frame element and it's children. This * method calls the abstract loadOdfFrameElement() method. * * @return false if loading failed */ virtual bool loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context); protected: /** * Abstract method to handle loading of the defined inner element like * e.g. the draw:image element. * @return false if loading failed */ virtual bool loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; private: class Private; Private * const d; }; #endif /* KOFRAMESHAPE_H */ diff --git a/libs/flake/KoMarker.h b/libs/flake/KoMarker.h index a50bc27b4f..2413ced08b 100644 --- a/libs/flake/KoMarker.h +++ b/libs/flake/KoMarker.h @@ -1,122 +1,123 @@ /* This file is part of the KDE project Copyright (C) 2011 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 KOMARKER_H #define KOMARKER_H #include #include #include "kritaflake_export.h" #include -class KoXmlElement; +#include + class KoShapeLoadingContext; class KoShapeSavingContext; class QString; class QPainterPath; class KoShape; class QPainter; class KoShapeStroke; class KRITAFLAKE_EXPORT KoMarker : public QSharedData { public: KoMarker(); ~KoMarker(); /** * Display name of the marker * * @return Display name of the marker */ QString name() const; KoMarker(const KoMarker &rhs); bool operator==(const KoMarker &other) const; enum MarkerCoordinateSystem { StrokeWidth, UserSpaceOnUse }; void setCoordinateSystem(MarkerCoordinateSystem value); MarkerCoordinateSystem coordinateSystem() const; static MarkerCoordinateSystem coordinateSystemFromString(const QString &value); static QString coordinateSystemToString(MarkerCoordinateSystem value); void setReferencePoint(const QPointF &value); QPointF referencePoint() const; void setReferenceSize(const QSizeF &size); QSizeF referenceSize() const; bool hasAutoOtientation() const; void setAutoOrientation(bool value); // measured in radians! qreal explicitOrientation() const; // measured in radians! void setExplicitOrientation(qreal value); void setShapes(const QList &shapes); QList shapes() const; /** * @brief paintAtOrigin paints the marker at the position \p pos. * Scales and rotates the masrker if needed. */ void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle); /** * Return maximum distance that the marker can take outside the shape itself */ qreal maxInset(qreal strokeWidth) const; /** * Bounding rect of the marker in local coordinates. It is assumed that the marker * is painted with the reference point placed at position (0,0) */ QRectF boundingRect(qreal strokeWidth, qreal nodeAngle) const; /** * Outline of the marker in local coordinates. It is assumed that the marker * is painted with the reference point placed at position (0,0) */ QPainterPath outline(qreal strokeWidth, qreal nodeAngle) const; /** * Draws a preview of the marker in \p previewRect of \p painter */ void drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position); void applyShapeStroke(KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle); private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoMarker*) #endif /* KOMARKER_H */ diff --git a/libs/flake/KoMarkerCollection.cpp b/libs/flake/KoMarkerCollection.cpp index 22c53dffca..f723f34dee 100644 --- a/libs/flake/KoMarkerCollection.cpp +++ b/libs/flake/KoMarkerCollection.cpp @@ -1,142 +1,139 @@ /* This file is part of the KDE project Copyright (C) 2011 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 "KoMarkerCollection.h" #include #include #include "KoMarker.h" #include #include #include #include #include #include #include #include "kis_debug.h" // WARNING: there is a bug in GCC! It doesn't warn that we are // deleting an uninitialized type here! #include class Q_DECL_HIDDEN KoMarkerCollection::Private { public: ~Private() { } QList > markers; }; KoMarkerCollection::KoMarkerCollection(QObject *parent) : QObject(parent) , d(new Private) { // Add no marker so the user can remove a marker from the line. d->markers.append(QExplicitlySharedDataPointer(0)); // Add default markers loadDefaultMarkers(); } KoMarkerCollection::~KoMarkerCollection() { delete d; } void KoMarkerCollection::loadMarkersFromFile(const QString &svgFile) { QFile file(svgFile); if (!file.exists()) return; if (!file.open(QIODevice::ReadOnly)) return; - QXmlStreamReader reader(&file); - reader.setNamespaceProcessing(false); - QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(&file, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << svgFile << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); return; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values parser.setXmlBaseDir(QFileInfo(svgFile).absolutePath()); parser.setFileFetcher( [](const QString &fileName) { QFile file(fileName); if (!file.exists()) return QByteArray(); file.open(QIODevice::ReadOnly); return file.readAll(); }); QSizeF fragmentSize; QList shapes = parser.parseSvg(doc.documentElement(), &fragmentSize); qDeleteAll(shapes); Q_FOREACH (const QExplicitlySharedDataPointer &marker, parser.knownMarkers()) { addMarker(marker.data()); } } void KoMarkerCollection::loadDefaultMarkers() { QString filePath = KoResourcePaths::findResource("data", "styles/markers.svg"); loadMarkersFromFile(filePath); } QList KoMarkerCollection::markers() const { QList markerList; foreach (const QExplicitlySharedDataPointer& m, d->markers){ markerList.append(m.data()); } return markerList; } KoMarker * KoMarkerCollection::addMarker(KoMarker *marker) { foreach (const QExplicitlySharedDataPointer& m, d->markers) { if (marker == m.data()) { return marker; } if (m && *marker == *m) { debugFlake << "marker is the same as other"; return m.data(); } } d->markers.append(QExplicitlySharedDataPointer(marker)); return marker; } diff --git a/libs/flake/KoMarkerCollection.h b/libs/flake/KoMarkerCollection.h index bd74001047..1f7115419d 100644 --- a/libs/flake/KoMarkerCollection.h +++ b/libs/flake/KoMarkerCollection.h @@ -1,68 +1,68 @@ /* This file is part of the KDE project Copyright (C) 2011 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 KOMARKERCOLLECTION_H #define KOMARKERCOLLECTION_H #include "kritaflake_export.h" #include #include #include #include class KoMarker; -class KoXmlElement; +#include class KoShapeLoadingContext; class KRITAFLAKE_EXPORT KoMarkerCollection : public QObject { Q_OBJECT public: explicit KoMarkerCollection(QObject *parent = 0); ~KoMarkerCollection() override; QList markers() const; /** * Add marker to collection * * The collection checks if a marker with the same content exists and if so deletes the * passed marker and returns a pointer to an existing marker. If no such marker exists it * adds the marker and return the same pointer as passed. * Calling that function passes ownership of the marker to this class. * * @param marker Marker to add * @return pointer to marker that should be used. This might be different to the marker passed */ KoMarker * addMarker(KoMarker *marker); void loadMarkersFromFile(const QString &svgFile); private: /// load the markers that are available per default. void loadDefaultMarkers(); class Private; Private * const d; }; Q_DECLARE_METATYPE(KoMarkerCollection *) #endif /* KOMARKERCOLLECTION_H */ diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h index 5b097f69db..d08f237fa5 100644 --- a/libs/flake/KoOdfGradientBackground.h +++ b/libs/flake/KoOdfGradientBackground.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOODFGRADIENTBACKGROUND_H #define KOODFGRADIENTBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" class QImage; class KoOdfGradientBackgroundPrivate; -class KoXmlElement; +#include class KoGenStyles; class KoGenStyle; /// Gradients from odf that are not native to Qt class KoOdfGradientBackground : public KoShapeBackground { public: // constructor KoOdfGradientBackground(); // destructor ~KoOdfGradientBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// reimplemented from KoShapeBackground void fillStyle(KoGenStyle& style, KoShapeSavingContext& context) override; /// reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) override; /// reimplemented from KoShapeBackground void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const override; private: bool loadOdf(const KoXmlElement &element); void saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const; void renderSquareGradient(QImage &buffer) const; void renderRectangleGradient(QImage &buffer) const; private: void debug() const; private: Q_DECLARE_PRIVATE(KoOdfGradientBackground) Q_DISABLE_COPY(KoOdfGradientBackground) }; #endif diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h index 684828c37d..038f4cf8c9 100644 --- a/libs/flake/KoOdfWorkaround.h +++ b/libs/flake/KoOdfWorkaround.h @@ -1,162 +1,162 @@ /* This file is part of the KDE project Copyright (C) 2009 Thorsten Zachmann Copyright (C) 2011 Jan Hambrecht 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 KOODFWORKAROUND_H #define KOODFWORKAROUND_H #include "kritaflake_export.h" #include "KoTextShapeDataBase.h" #include #include -class KoXmlElement; +#include class KoShape; class KoShapeLoadingContext; class QPen; class QColor; class QString; class KoColorBackground; /** * This class should contain all workarounds to correct problems with different ODF * implementations. If you need to access application specific things please create a * new namespace in the application you need it in * All calls to methods of this class should be wrapped into ifndefs like e.g. * * @code * #ifndef NWORKAROUND_ODF_BUGS * KoOdfWorkaround::fixPenWidth(pen, context); * #endif * @endcode */ namespace KoOdfWorkaround { /** * OpenOffice handles a line with the width of 0 as a cosmetic line but in svg it makes the line invisible. * To show it in calligra use a very small line width. However this is not a cosmetic line. */ KRITAFLAKE_EXPORT void fixPenWidth(QPen &pen, KoShapeLoadingContext &context); /** * OpenOffice < 3.0 does not store the draw:enhanced-path for draw:type="ellipse" * Add the path needed for the ellipse */ KRITAFLAKE_EXPORT void fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context); /** * OpenOffice interchanges the position coordinates for polar handles. * According to the specification the first coordinate is the angle, the * second coordinates is the radius. OpenOffice does it the other way around. */ KRITAFLAKE_EXPORT void fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape = 0); KRITAFLAKE_EXPORT QColor fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT QSharedPointer fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context); /** * Old versions of ooimpress does not set the placeholder for shapes that should have it set * See open office issue http://www.openoffice.org/issues/show_bug.cgi?id=96406 * And kde bug https://bugs.kde.org/show_bug.cgi?id=185354 */ KRITAFLAKE_EXPORT void setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixPresentationPlaceholder(); KRITAFLAKE_EXPORT void fixPresentationPlaceholder(KoShape *shape); /** * OpenOffice and LibreOffice save gluepoint positions wrong when no align is specified. * According to the specification for the above situation, the position should be saved * as percent values relative to the shapes center point. OpenOffice seems to write * these percent values converted to length units, where the millimeter value corresponds * to the correct percent value (i.e. -5cm = -50mm = -50%). */ KRITAFLAKE_EXPORT void fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context); /** * OpenOffice and LibreOffice does not conform to the specification about default value * of the svg:fill-rule. If this attribute is missing, according the spec, the initial * value is nonzero, but OOo uses evenodd. Because we are conform to the spec, we need * to set what OOo display. * See http://www.w3.org/TR/SVG/painting.html#FillRuleProperty */ KRITAFLAKE_EXPORT void fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context); /** - * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to + * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to * small the text will not fit and it needs to be adjusted during the first layout. * This methods returns true if we need to adjust the layout. The adjusting is handled at a different place. */ KRITAFLAKE_EXPORT bool fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context); /** * OpenOffice and LibreOffice do not set the svg:width, svg:height, svg:x and svg:y correctly when saving * parts of draw:ellipses or draw:circle * This method returns true when the width, height, x and y is given for the full circle */ KRITAFLAKE_EXPORT bool fixEllipse(const QString &kind, KoShapeLoadingContext &context); /** * Calligra did use the bad strings "Formula.hidden" and "protected Formula.hidden" as values * for style:cell-protect, instead of "formula-hidden" and "protected formula-hidden". * This method fixes the bad strings to the correct ones. */ KRITAFLAKE_EXPORT void fixBadFormulaHiddenForStyleCellProtect(QString &value); /** * Calligra used to store text:time-value with a "0-00-00T" prefix * This method removes that prefix. */ KRITAFLAKE_EXPORT void fixBadDateForTextTime(QString &value); /** * OpenOffice.org used to write the "rect(...)" value for fo:clip without * separating the 4 offset values by commas. * This method changes the string with the offset values to have commas as separators. */ KRITAFLAKE_EXPORT void fixClipRectOffsetValuesString(QString &offsetValuesString); /** * LibreOffice used to write text:style-name attribute for table:table-template element, * which is not a valid attribute for the element. */ KRITAFLAKE_EXPORT QString fixTableTemplateName(const KoXmlElement &e); /** * LibreOffice used to write text:style-name attribute for * table:first-row, table:last-row, table:first-column, * table:last-column, table:odd-rows, table:odd-columns, * table:body elements, which is not a valid attribute for the element. */ KRITAFLAKE_EXPORT QString fixTableTemplateCellStyleName(const KoXmlElement &e); /** * LibreOffice used to have a bug with handling of z command in svg path. * This resulted in broken marker path used (and copied also to Calligra). * This methods substitutes known old marker paths with the latest (fixed) * path variant. */ KRITAFLAKE_EXPORT void fixMarkerPath(QString &path); } #endif /* KOODFWORKAROUND_H */ diff --git a/libs/flake/KoShapeAnchor.h b/libs/flake/KoShapeAnchor.h index 8a3e700416..06b3465e7e 100644 --- a/libs/flake/KoShapeAnchor.h +++ b/libs/flake/KoShapeAnchor.h @@ -1,262 +1,262 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2011 Matus Hanzes * Copyright (C) 2013 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPEANCHOR_H #define KOSHAPEANCHOR_H #include "kritaflake_export.h" class KoShape; -class KoXmlElement; +#include class KoShapeLoadingContext; class KoShapeSavingContext; class KoShapeAnchorPrivate; class QTextDocument; class QPointF; class QString; /** * This class is the object that explains how a shape is anchored to something. * * The anchored shape will be positioned (in supporting applications) based on the properties * defined in this class. * * This class can be used in three different ways: * -page anchor * -as-char * -char, paragraph anchor * * If it's a page anchor it just provide the info about how the shape relates to a page with a specific * page number. * * For the other types of anchoring it has to have a TextLocation in a QTextDocument. This TextLocation * can either be an inline character (type as-char) or a position (type char or paragraph) The * KoShapeAnchor and TextLocation connects the anchored-shape to the text flow so the anchored shape * can be repositioned on the canvas if new text is inserted or removed before the anchor character. * * For as-char, char and paragraph use cases: * @see KoAnchorInlineObject * @see KoAnchorTextRange * which are both implemented as subclasses of TextLocation * * The position of the shape relative to the anchor is called the offset. It's loaded by loadOdf(). * @see PlacementStrategy for more information about the layout of anchors/shapes. */ class KRITAFLAKE_EXPORT KoShapeAnchor { public: /** * This class is an interface that positions the shape linked to text anchor */ class PlacementStrategy { public: PlacementStrategy(){}; virtual ~PlacementStrategy(){}; /** * Reparent the anchored shape to not have a parent shape container (and model) * */ virtual void detachFromModel() = 0; /** * Reparent the anchored shape under an appropriate shape container (and model) * * If needed, it changes the parent KoShapeContainerModel and KoShapeContainer of the anchored * shape. */ virtual void updateContainerModel() = 0; }; class TextLocation { public: TextLocation(){}; virtual ~TextLocation(){}; virtual const QTextDocument *document() const = 0; virtual int position() const = 0; }; enum HorizontalPos { HCenter, HFromInside, HFromLeft, HInside, HLeft, HOutside, HRight }; enum HorizontalRel { //NOTE: update KWAnchoringProperties if you change this HChar, HPage, HPageContent, HPageStartMargin, HPageEndMargin, HFrame, HFrameContent, HFrameEndMargin, HFrameStartMargin, HParagraph, HParagraphContent, HParagraphEndMargin, HParagraphStartMargin }; enum VerticalPos { VBelow, VBottom, VFromTop, VMiddle, VTop }; enum VerticalRel { //NOTE: update KWAnchoringProperties if you change this VBaseline, VChar, VFrame, VFrameContent, VLine, VPage, VPageContent, VParagraph, VParagraphContent, VText }; enum AnchorType { AnchorAsCharacter, AnchorToCharacter, AnchorParagraph, AnchorPage }; /** * Constructor for an in-place anchor. * @param shape the anchored shape that this anchor links to. */ explicit KoShapeAnchor(KoShape *shape); virtual ~KoShapeAnchor(); /** * Return the shape that is linked to from the text anchor. */ KoShape *shape() const; /** * Returns the type of the anchor. * * The text:anchor-type attribute specifies how a frame is bound to a * text document. The anchor position is the point at which a frame is * bound to a text document. The defined values for the text:anchor-type * attribute are; * * - as-char * There is no anchor position. The drawing shape behaves like a * character. * - char * The character after the drawing shape element. * - frame * The parent text box that the current drawing shape element is * contained in. * FIXME we don't support type frame * - page * The page that has the same physical page number as the value of the * text:anchor-page-number attribute that is attached to the drawing * shape element. * - paragraph * The paragraph that the current drawing shape element is contained in. */ AnchorType anchorType() const; /** * Set how the anchor behaves */ void setAnchorType(AnchorType type); /// set the current vertical-pos void setHorizontalPos(HorizontalPos); /// return the current vertical-pos HorizontalPos horizontalPos() const; /// set the current vertical-rel void setHorizontalRel(HorizontalRel); /// return the current vertical-rel HorizontalRel horizontalRel() const; /// set the current horizontal-pos void setVerticalPos(VerticalPos); /// return the current horizontal-pos VerticalPos verticalPos() const; /// set the current horizontal-rel void setVerticalRel(VerticalRel); /// return the current horizontal-rel VerticalRel verticalRel() const; /// return the wrap influence on position QString wrapInfluenceOnPosition() const; /// return if flow-with-text (odf attribute) bool flowWithText() const; /// return the page number of the shape (valid with page anchoring, -1 indicates auto). int pageNumber() const; /// return the offset of the shape from the anchor. const QPointF &offset() const; /// set the new offset of the shape. Causes a new layout soon. void setOffset(const QPointF &offset); /// Load the additional attributes. /// This will also make the shape invisible so it doesn't mess up any layout /// before it's ready to be placed where it belongs /// The textlayout should make it visible again bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context); /// Save the additional attributes. void saveOdf(KoShapeSavingContext &context) const; /// Get extra data structure that is what is actually inside a text document TextLocation *textLocation() const; /// Set extra data structure that is what is actually inside a text document /// We do NOT take ownership (may change in the future) void setTextLocation(TextLocation *textLocation); /// Get placement strategy which is used to position shape linked to text anchor PlacementStrategy *placementStrategy() const; /// Set placement strategy which is used to position shape linked to text anchor /// We take owner ship and will make sure the strategy is deleted void setPlacementStrategy(PlacementStrategy *placementStrategy); private: class Private; Private * const d; }; #endif diff --git a/libs/flake/KoShapeSavingContext.cpp b/libs/flake/KoShapeSavingContext.cpp index fa8a8f4f3b..caa3122743 100644 --- a/libs/flake/KoShapeSavingContext.cpp +++ b/libs/flake/KoShapeSavingContext.cpp @@ -1,346 +1,346 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007-2009, 2011 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht 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 "KoShapeSavingContext.h" #include "KoDataCenterBase.h" #include "KoShapeLayer.h" #include "KoImageData.h" #include "KoMarker.h" #include #include #include #include #include #include #include #include #include class KoShapeSavingContextPrivate { public: KoShapeSavingContextPrivate(KoXmlWriter&, KoGenStyles&, KoEmbeddedDocumentSaver&); ~KoShapeSavingContextPrivate(); KoXmlWriter *xmlWriter; KoShapeSavingContext::ShapeSavingOptions savingOptions; QList layers; QSet dataCenters; QMap sharedData; QMap imageNames; int imageId; QMap images; QHash shapeOffsets; QMap markerRefs; KoGenStyles& mainStyles; KoEmbeddedDocumentSaver& embeddedSaver; QMap references; QMap referenceCounters; QMap > prefixedReferences; }; KoShapeSavingContextPrivate::KoShapeSavingContextPrivate(KoXmlWriter &w, KoGenStyles &s, KoEmbeddedDocumentSaver &e) : xmlWriter(&w), savingOptions(0), imageId(0), mainStyles(s), embeddedSaver(e) { } KoShapeSavingContextPrivate::~KoShapeSavingContextPrivate() { Q_FOREACH (KoSharedSavingData * data, sharedData) { delete data; } } KoShapeSavingContext::KoShapeSavingContext(KoXmlWriter &xmlWriter, KoGenStyles &mainStyles, KoEmbeddedDocumentSaver &embeddedSaver) : d(new KoShapeSavingContextPrivate(xmlWriter, mainStyles, embeddedSaver)) { // by default allow saving of draw:id + xml:id addOption(KoShapeSavingContext::DrawId); } KoShapeSavingContext::~KoShapeSavingContext() { delete d; } KoXmlWriter & KoShapeSavingContext::xmlWriter() { return *d->xmlWriter; } void KoShapeSavingContext::setXmlWriter(KoXmlWriter &xmlWriter) { d->xmlWriter = &xmlWriter; } KoGenStyles & KoShapeSavingContext::mainStyles() { return d->mainStyles; } KoEmbeddedDocumentSaver &KoShapeSavingContext::embeddedSaver() { return d->embeddedSaver; } bool KoShapeSavingContext::isSet(ShapeSavingOption option) const { return d->savingOptions & option; } void KoShapeSavingContext::setOptions(ShapeSavingOptions options) { d->savingOptions = options; } KoShapeSavingContext::ShapeSavingOptions KoShapeSavingContext::options() const { return d->savingOptions; } void KoShapeSavingContext::addOption(ShapeSavingOption option) { d->savingOptions = d->savingOptions | option; } void KoShapeSavingContext::removeOption(ShapeSavingOption option) { if (isSet(option)) d->savingOptions = d->savingOptions ^ option; // xor to remove it. } KoElementReference KoShapeSavingContext::xmlid(const void *referent, const QString& prefix, KoElementReference::GenerationOption counter) { Q_ASSERT(counter == KoElementReference::UUID || (counter == KoElementReference::Counter && !prefix.isEmpty())); if (d->references.contains(referent)) { return d->references[referent]; } KoElementReference ref; if (counter == KoElementReference::Counter) { int referenceCounter = d->referenceCounters[prefix]; referenceCounter++; ref = KoElementReference(prefix, referenceCounter); d->references.insert(referent, ref); d->referenceCounters[prefix] = referenceCounter; } else { if (!prefix.isEmpty()) { ref = KoElementReference(prefix); d->references.insert(referent, ref); } else { d->references.insert(referent, ref); } } if (!prefix.isNull()) { d->prefixedReferences[prefix].append(referent); } return ref; } KoElementReference KoShapeSavingContext::existingXmlid(const void *referent) { if (d->references.contains(referent)) { return d->references[referent]; } else { KoElementReference ref; ref.invalidate(); return ref; } } void KoShapeSavingContext::clearXmlIds(const QString &prefix) { if (d->prefixedReferences.contains(prefix)) { Q_FOREACH (const void* ptr, d->prefixedReferences[prefix]) { d->references.remove(ptr); } d->prefixedReferences.remove(prefix); } if (d->referenceCounters.contains(prefix)) { d->referenceCounters[prefix] = 0; } } void KoShapeSavingContext::addLayerForSaving(const KoShapeLayer *layer) { if (layer && ! d->layers.contains(layer)) d->layers.append(layer); } void KoShapeSavingContext::saveLayerSet(KoXmlWriter &xmlWriter) const { xmlWriter.startElement("draw:layer-set"); Q_FOREACH (const KoShapeLayer * layer, d->layers) { xmlWriter.startElement("draw:layer"); xmlWriter.addAttribute("draw:name", layer->name()); if (layer->isGeometryProtected()) xmlWriter.addAttribute("draw:protected", "true"); if (! layer->isVisible()) xmlWriter.addAttribute("draw:display", "none"); xmlWriter.endElement(); // draw:layer } xmlWriter.endElement(); // draw:layer-set } void KoShapeSavingContext::clearLayers() { d->layers.clear(); } QString KoShapeSavingContext::imageHref(const KoImageData *image) { QMap::iterator it(d->imageNames.find(image->key())); if (it == d->imageNames.end()) { QString suffix = image->suffix(); if (suffix.isEmpty()) { it = d->imageNames.insert(image->key(), QString("Pictures/image%1").arg(++d->imageId)); } else { it = d->imageNames.insert(image->key(), QString("Pictures/image%1.%2").arg(++d->imageId).arg(suffix)); } } return it.value(); } QString KoShapeSavingContext::imageHref(const QImage &image) { // TODO this can be optimized to recognize images which have the same content // Also this can use quite a lot of memeory as the qimage are all kept until // they are saved to the store in memory QString href = QString("Pictures/image%1.png").arg(++d->imageId); d->images.insert(href, image); return href; } QMap KoShapeSavingContext::imagesToSave() { return d->imageNames; } -QString KoShapeSavingContext::markerRef(const KoMarker *marker) +QString KoShapeSavingContext::markerRef(const KoMarker */*marker*/) { // QMap::iterator it = d->markerRefs.find(marker); // if (it == d->markerRefs.end()) { // it = d->markerRefs.insert(marker, marker->saveOdf(*this)); // } // return it.value(); return QString(); } void KoShapeSavingContext::addDataCenter(KoDataCenterBase * dataCenter) { if (dataCenter) { d->dataCenters.insert(dataCenter); } } bool KoShapeSavingContext::saveDataCenter(KoStore *store, KoXmlWriter* manifestWriter) { bool ok = true; Q_FOREACH (KoDataCenterBase *dataCenter, d->dataCenters) { ok = ok && dataCenter->completeSaving(store, manifestWriter, this); //debugFlake << "ok" << ok; } // Save images for (QMap::iterator it(d->images.begin()); it != d->images.end(); ++it) { if (store->open(it.key())) { KoStoreDevice device(store); ok = ok && it.value().save(&device, "PNG"); store->close(); // TODO error handling if (ok) { const QString mimetype = KisMimeDatabase::mimeTypeForFile(it.key()); manifestWriter->addManifestEntry(it.key(), mimetype); } else { warnFlake << "saving image failed"; } } else { ok = false; warnFlake << "saving image failed: open store failed"; } } return ok; } void KoShapeSavingContext::addSharedData(const QString &id, KoSharedSavingData * data) { QMap::iterator it(d->sharedData.find(id)); // data will not be overwritten if (it == d->sharedData.end()) { d->sharedData.insert(id, data); } else { warnFlake << "The id" << id << "is already registered. Data not inserted"; Q_ASSERT(it == d->sharedData.end()); } } KoSharedSavingData * KoShapeSavingContext::sharedData(const QString &id) const { KoSharedSavingData * data = 0; QMap::const_iterator it(d->sharedData.constFind(id)); if (it != d->sharedData.constEnd()) { data = it.value(); } return data; } void KoShapeSavingContext::addShapeOffset(const KoShape *shape, const QTransform &m) { d->shapeOffsets.insert(shape, m); } void KoShapeSavingContext::removeShapeOffset(const KoShape *shape) { d->shapeOffsets.remove(shape); } QTransform KoShapeSavingContext::shapeOffset(const KoShape *shape) const { return d->shapeOffsets.value(shape, QTransform()); } diff --git a/libs/flake/KoTextShapeDataBase.h b/libs/flake/KoTextShapeDataBase.h index 14236e49fc..a5aab94bc1 100644 --- a/libs/flake/KoTextShapeDataBase.h +++ b/libs/flake/KoTextShapeDataBase.h @@ -1,145 +1,145 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTSHAPEDATABASE_H #define KOTEXTSHAPEDATABASE_H #include "kritaflake_export.h" #include "KoShapeUserData.h" class KoTextShapeDataBasePrivate; -class KoXmlElement; +#include class KoShapeLoadingContext; class KoShapeSavingContext; class KoGenStyle; struct KoInsets; class QTextDocument; /** * \internal */ class KRITAFLAKE_EXPORT KoTextShapeDataBase : public KoShapeUserData { Q_OBJECT public: /// constructor KoTextShapeDataBase(); ~KoTextShapeDataBase() override; /// return the document QTextDocument *document() const; /** * Set the margins that will make the shapes text area smaller. * The shape that owns this textShapeData object will layout text in an area * confined by the shape size made smaller by the margins set here. * @param margins the margins that shrink the text area. */ void setShapeMargins(const KoInsets &margins); /** * returns the currently set margins for the shape. */ KoInsets shapeMargins() const; /** * Load the text from ODF. */ virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * Save the text to ODF. */ virtual void saveOdf(KoShapeSavingContext &context, int from = 0, int to = -1) const = 0; /** * Load the style of the element * * This method is used to load the style in case the TextShape is used as TOS. In this case * the paragraph style of the shape e.g. a custom-shape needs to be applied before we load the * text so all looks as it should look. */ virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * Save the style of the element * * This method save the style in case the TextShape is used as TOS. In this case the paragraph * style of the shape e.g. a custom-shape needs to be saved with the style of the shape. */ virtual void saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0; /** Sets the vertical alignment of all the text inside the shape. */ void setVerticalAlignment(Qt::Alignment alignment); /** Returns the vertical alignment of all the text in the shape */ Qt::Alignment verticalAlignment() const; /** * Enum to describe the text document's automatic resizing behaviour. */ enum ResizeMethod { /// Resize the shape to fit the content. This makes sure that the text shape takes op /// only as much space as absolutely necessary to fit the entire text into its boundaries. AutoResize, /// Specifies whether or not to automatically increase the width of the drawing object /// if text is added to fit the entire width of the text into its boundaries. /// Compared to AutoResize above this only applied to the width whereas the height is /// not resized. Also this only grows but does not shrink again if text is removed again. AutoGrowWidth, /// Specifies whether or not to automatically increase the height of the drawing object /// if text is added to fit the entire height of the text into its boundaries. AutoGrowHeight, /// This combines the AutoGrowWidth and AutoGrowHeight and automatically increase width /// and height to fit the entire text into its boundaries. AutoGrowWidthAndHeight, /// Shrink the content displayed within the shape to match into the shape's boundaries. This /// will scale the content down as needed to display the whole document. ShrinkToFitResize, /// Deactivates auto-resizing. This is the default resizing method. NoResize }; /** * Specifies how the document should be resized upon a change in the document. * * If auto-resizing is turned on, text will not be wrapped unless enforced by e.g. a newline. * * By default, NoResize is set. */ void setResizeMethod(ResizeMethod method); /** * Returns the auto-resizing mode. By default, this is NoResize. * * @see setResizeMethod */ ResizeMethod resizeMethod() const; protected: /// constructor KoTextShapeDataBase(KoTextShapeDataBasePrivate *); KoTextShapeDataBasePrivate *d_ptr; private: Q_DECLARE_PRIVATE(KoTextShapeDataBase) }; #endif diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp index 240a9d2ba1..1c85d0f7b2 100644 --- a/libs/flake/KoUnavailShape.cpp +++ b/libs/flake/KoUnavailShape.cpp @@ -1,656 +1,628 @@ /* This file is part of the KDE project * * Copyright (C) 2010-2011 Inge Wallin * * 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. */ // Own #include "KoUnavailShape.h" // Qt #include #include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "SimpleShapeContainerModel.h" #include "KoShapeBackground.h" #include // The XML of a frame looks something like this: -// +// // 1. // 2. // 3. // 4. // // or -// +// // 1. -// 2. ...inline xml here... +// 2. ...inline xml here... // 3. // 4. // // We define each Xml statement on lines 2 and 3 above as an "object". // (Strictly only the first child element is an object in the ODF sense, // but we have to have some terminology here.) -// +// // In an ODF frame, only the first line, i.e. the first object // contains the real contents. All the rest of the objects are used / // shown if we cannot handle the first one. The most common cases are // that there is only one object inside the frame OR that there are 2 // and the 2nd is a picture. // // Sometimes, e.g. in the case of an embedded document, the reference // points not to a file but to a directory structure inside the ODF -// store. +// store. // // When we load and save in the UnavailShape, we have to be general // enough to cover all possible cases of references and inline XML, // embedded files and embedded directory structures. // // We also have to be careful because we cannot reuse the object names // that are in the original files when saving. Instead we need to // create new object names because the ones that were used in the // original file may already be used by other embedded files/objects // that are saved by other shapes. // // FIXME: There should only be ONE place where new object / file names // are generated, not 2(?) like there are now: // KoEmbeddedDocumentSaver and the KoImageCollection. // // An ObjectEntry is used to store information about objects in the // frame, as defined above. struct ObjectEntry { QByteArray objectXmlContents; // the XML tree in the object QString objectName; // object name in the frame without "./" // This is extracted from objectXmlContents. bool isDir; KoOdfManifestEntry *manifestEntry; // manifest entry for the above. }; // A FileEntry is used to store information about embedded files // inside (i.e. referred to by) an object. struct FileEntry { QString path; // Normalized filename, i.e. without "./". QString mimeType; bool isDir; QByteArray contents; }; class KoUnavailShape::Private { public: Private(KoUnavailShape* qq); ~Private(); void draw(QPainter &painter) const; void drawNull(QPainter &painter) const; void storeObjects(const KoXmlElement &element); void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces); void storeFile(const QString &filename, KoShapeLoadingContext &context); QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context); // Objects inside the frame. For each file, we store: // - The XML code for the object // - Any embedded files (names, contents) that are referenced by xlink:href // - Whether they are directories, i.e. if they contain a file tree and not just one file. // - The manifest entries QList objectEntries; // Embedded files QList embeddedFiles; // List of embedded files. // Some cached values. QPixmap questionMark; QPixmap pixmapPreview; QSvgRenderer *scalablePreview; KoUnavailShape* q; }; KoUnavailShape::Private::Private(KoUnavailShape* qq) : scalablePreview(new QSvgRenderer()) , q(qq) { // Get the question mark "icon". questionMark.load(":/questionmark.png"); } KoUnavailShape::Private::~Private() { qDeleteAll(objectEntries); qDeleteAll(embeddedFiles); // It's a QObject, but we haven't parented it. delete(scalablePreview); } // ---------------------------------------------------------------- // The main class KoUnavailShape::KoUnavailShape() : KoFrameShape( "", "" ) , KoShapeContainer(new SimpleShapeContainerModel()) , d(new Private(this)) { setShapeId(KoUnavailShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) ); } KoUnavailShape::~KoUnavailShape() { delete d; } void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { applyConversion(painter, converter); // If the frame is empty, just draw a background. debugFlake << "Number of objects:" << d->objectEntries.size(); if (d->objectEntries.isEmpty()) { // But... only try to draw the background if there's one such if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } } else { if(shapes().isEmpty()) { d->draw(painter); } } } void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoUnavailShape::Private::draw(QPainter &painter) const { painter.save(); painter.setRenderHint(QPainter::Antialiasing); // Run through the previews in order of preference. Draw a placeholder // questionmark if there is no preview available for rendering. if (scalablePreview->isValid()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); scalablePreview->render(&painter, bounds); } else if (!pixmapPreview.isNull()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.drawPixmap(bounds, pixmapPreview); } else if (q->shapes().isEmpty()) { // Draw a nice question mark with a frame around it if there // is no other preview image. If there is a contained image // shape, we don't need to draw anything. // Get the question mark "icon". // FIXME: We should be able to use d->questionMark here. QPixmap questionMark; questionMark.load(":/questionmark.png"); // The size of the image is: // - the size of the shape if shapesize < 2cm // - 2 cm if 2cm <= shapesize <= 8cm // - shapesize / 4 if shapesize > 8cm qreal width = q->size().width(); qreal height = q->size().height(); qreal picSize = CM_TO_POINT(2); // Default size is 2 cm. if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2)) picSize = qMin(width, height); else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8)) picSize = qMin(width, height) / qreal(4.0); painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0), picSize, picSize, questionMark); // Draw a gray rectangle around the shape. painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(QRectF(QPointF(0,0), q->size())); } painter.restore(); } void KoUnavailShape::Private::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), q->size()); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } // ---------------------------------------------------------------- // Loading and Saving void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const { debugFlake << "START SAVING ##################################################"; KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes( context, OdfAllAttributes ); // Write the stored XML to the file, but don't reuse object names. int lap = 0; QString newName; foreach (const ObjectEntry *object, d->objectEntries) { QByteArray xmlArray(object->objectXmlContents); QString objectName(object->objectName); // Possibly empty. KoOdfManifestEntry *manifestEntry(object->manifestEntry); // Create a name for this object. If this is not the first // object, i.e. a replacement object (most likely a picture), // then reuse the name but put it in ReplacementObjects. if (++lap == 1) { // The first lap in the loop is the actual object. All // other laps are replacement objects. newName = fileSaver.getFilename("Object "); } else if (lap == 2) { newName = "ObjectReplacements/" + newName; } else // FIXME: what should replacement 2 and onwards be called? newName = newName + "_"; // If there was a previous object name, replace it with the new one. if (!objectName.isEmpty() && manifestEntry) { // FIXME: We must make a copy of the byte array here because // otherwise we won't be able to save > 1 time. xmlArray.replace(objectName.toLatin1(), newName.toLatin1()); } writer.addCompleteElement(xmlArray.data()); // If the objectName is empty, this may be inline XML. // If so, we are done now. if (objectName.isEmpty() || !manifestEntry) { continue; } // Save embedded files for this object. foreach (FileEntry *entry, d->embeddedFiles) { QString fileName(entry->path); // If we found a file for this object, we need to write it // but with the new object name instead of the old one. if (!fileName.startsWith(objectName)) continue; debugFlake << "Object name: " << objectName << "newName: " << newName << "filename: " << fileName << "isDir: " << entry->isDir; fileName.replace(objectName, newName); fileName.prepend("./"); debugFlake << "New filename: " << fileName; // FIXME: Check if we need special treatment of directories. fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents); } // Write the manifest entry for the object itself. If it's a // file, the manifest is already written by saveFile, so skip // it here. if (object->isDir) { fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(), manifestEntry->version()); } } writer.endElement(); // draw:frame } bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context) { debugFlake << "START LOADING ##################################################"; //debugFlake << "Loading ODF frame in the KoUnavailShape. Element = " // << frameElement.tagName(); loadOdfAttributes(frameElement, context, OdfAllAttributes); // NOTE: We cannot use loadOdfFrame() because we want to save all // the things inside the frame, not just one of them, like // loadOdfFrame() provides. // Get the manifest. QList manifest = context.odfLoadingContext().manifestEntries(); #if 0 // Enable to show all manifest entries. debugFlake << "MANIFEST: "; foreach (KoOdfManifestEntry *entry, manifest) { debugFlake << entry->mediaType() << entry->fullPath() << entry->version(); } #endif // 1. Get the XML contents of the objects from the draw:frame. As // a side effect, this extracts the object names from all // xlink:href and stores them into d->objectNames. The saved // xml contents itself is saved into d->objectXmlContents // (QByteArray) so we can save it back from saveOdf(). d->storeObjects(frameElement); #if 1 // Debug only debugFlake << "----------------------------------------------------------------"; debugFlake << "After storeObjects():"; foreach (ObjectEntry *object, d->objectEntries) { debugFlake << "objectXmlContents: " << object->objectXmlContents << "objectName: " << object->objectName; // Note: at this point, isDir and manifestEntry are not set. #endif } // 2. Loop through the objects that were found in the frame and // save all the files associated with them. Some of the // objects are files, and some are directories. The // directories are searched and the files within are saved as // well. // // In this loop, isDir and manifestEntry of each ObjectEntry are set. bool foundPreview = false; foreach (ObjectEntry *object, d->objectEntries) { QString objectName = object->objectName; if (objectName.isEmpty()) continue; debugFlake << "Storing files for object named:" << objectName; // Try to find out if the entry is a directory. // If the object is a directory, then save all the files // inside it, otherwise save the file as it is. QString dirName = objectName + '/'; bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty(); if (isDir) { // A directory: the files can be found in the manifest. foreach (KoOdfManifestEntry *entry, manifest) { if (entry->fullPath() == dirName) continue; if (entry->fullPath().startsWith(dirName)) { d->storeFile(entry->fullPath(), context); } } } else { // A file: save it. d->storeFile(objectName, context); } // Get the manifest entry for this object. KoOdfManifestEntry *entry = 0; QString entryName = isDir ? dirName : objectName; for (int j = 0; j < manifest.size(); ++j) { KoOdfManifestEntry *temp = manifest.value(j); if (temp->fullPath() == entryName) { entry = new KoOdfManifestEntry(*temp); break; } } object->isDir = isDir; object->manifestEntry = entry; // If we have not already found a preview in previous times // through the loop, then see if this one may be a preview. if (!foundPreview) { debugFlake << "Attempting to load preview from " << objectName; QByteArray previewData = d->loadFile(objectName, context); // Check to see if we know the mimetype for this entry. Specifically: // 1. Check to see if the item is a loadable SVG file // FIXME: Perhaps check in the manifest first? But this // seems to work well. d->scalablePreview->load(previewData); if (d->scalablePreview->isValid()) { debugFlake << "Found scalable preview image!"; d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg")); foundPreview = true; continue; } // 2. Otherwise check to see if it's a loadable pixmap file d->pixmapPreview.loadFromData(previewData); if (!d->pixmapPreview.isNull()) { debugFlake << "Found pixel based preview image!"; foundPreview = true; } } } #if 0 // Enable to get more detailed debug messages debugFlake << "Object manifest entries:"; for (int i = 0; i < d->manifestEntries.size(); ++i) { KoOdfManifestEntry *entry = d->manifestEntries.value(i); debugFlake << i << ":" << entry; if (entry) debugFlake << entry->fullPath() << entry->mediaType() << entry->version(); else debugFlake << "--"; } debugFlake << "END LOADING ####################################################"; #endif return true; } // Load the actual contents inside the frame. bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/, KoShapeLoadingContext &/*context*/) { return true; } // ---------------------------------------------------------------- // Private functions void KoUnavailShape::Private::storeObjects(const KoXmlElement &element) { // Loop through all the child elements of the draw:frame and save them. KoXmlNode n = element.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { debugFlake << "In draw:frame, node =" << n.nodeName(); // This disregards #text, but that's not in the spec anyway so // it doesn't need to be saved. if (!n.isElement()) continue; KoXmlElement el = n.toElement(); ObjectEntry *object = new ObjectEntry; QByteArray contentsTmp; QBuffer buffer(&contentsTmp); // the member KoXmlWriter writer(&buffer); // 1. Find out the objectName // Save the normalized filename, i.e. without a starting "./". // An empty string is saved if no name is found. QString name = el.attributeNS(KoXmlNS::xlink, "href", QString()); if (name.startsWith(QLatin1String("./"))) name.remove(0, 2); object->objectName = name; // 2. Copy the XML code. QHash unknownNamespaces; storeXmlRecursive(el, writer, object, unknownNamespaces); object->objectXmlContents = contentsTmp; // 3, 4: the isDir and manifestEntry members are not set here, // but initialize them anyway. . object->isDir = false; // Has to be initialized to something. object->manifestEntry = 0; objectEntries.append(object); } } void KoUnavailShape::Private::storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces) { // Start the element; // keep the name in a QByteArray so that it stays valid until end element is called. const QByteArray name(el.nodeName().toLatin1()); writer.startElement(name.constData()); - // Copy all the attributes, including namespaces. - QList< QPair > attributeNames = el.attributeFullNames(); - for (int i = 0; i < attributeNames.size(); ++i) { - QPair attrPair(attributeNames.value(i)); - if (attrPair.first.isEmpty()) { - writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second)); - } - else { - // This somewhat convoluted code is because we need the - // namespace, not the namespace URI. - QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1()); - // in case we don't find the namespace in our list create a own one and use that - // so the document created on saving is valid. - if (nsShort.isEmpty()) { - nsShort = unknownNamespaces.value(attrPair.first); - if (nsShort.isEmpty()) { - nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1); - unknownNamespaces.insert(attrPair.first, nsShort); - } - QString name = QString("xmlns:") + nsShort; - writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1()); - } - QString attr(nsShort + ':' + attrPair.second); - writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first, - attrPair.second)); - } - } - // Child elements // Loop through all the child elements of the draw:frame. KoXmlNode n = el.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces); } else if (n.isText()) { writer.addTextNode(n.toText().data()/*.toUtf8()*/); } } // End the element writer.endElement(); } /** * This function stores the embedded file in an internal store - it does not save files to disk, * and thus it is named in this manner, to avoid the function being confused with functions which * save files to disk. */ void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context) { debugFlake << "Saving file: " << fileName; // Directories need to be saved too, but they don't have any file contents. if (fileName.endsWith('/')) { FileEntry *entry = new FileEntry; entry->path = fileName; entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = true; embeddedFiles.append(entry); } QByteArray fileContent = loadFile(fileName, context); if (fileContent.isNull()) return; // Actually store the file in the list. FileEntry *entry = new FileEntry; entry->path = fileName; if (entry->path.startsWith(QLatin1String("./"))) entry->path.remove(0, 2); entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = false; entry->contents = fileContent; embeddedFiles.append(entry); debugFlake << "File length: " << fileContent.size(); } QByteArray KoUnavailShape::Private::loadFile(const QString &fileName, KoShapeLoadingContext &context) { // Can't load a file which is a directory, return an invalid QByteArray if (fileName.endsWith('/')) return QByteArray(); KoStore *store = context.odfLoadingContext().store(); QByteArray fileContent; if (!store->open(fileName)) { store->close(); return QByteArray(); } int fileSize = store->size(); fileContent = store->read(fileSize); store->close(); //debugFlake << "File content: " << fileContent; return fileContent; } diff --git a/libs/flake/svg/SvgCssHelper.h b/libs/flake/svg/SvgCssHelper.h index a3f26dfc32..5ddd5b3141 100644 --- a/libs/flake/svg/SvgCssHelper.h +++ b/libs/flake/svg/SvgCssHelper.h @@ -1,48 +1,48 @@ /* 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. */ #ifndef SVGCSSHELPER_H #define SVGCSSHELPER_H #include -class KoXmlElement; +#include class SvgCssHelper { public: SvgCssHelper(); ~SvgCssHelper(); /// Parses css style sheet in given xml element void parseStylesheet(const KoXmlElement &); /** * Matches css styles to given xml element and returns them * @param element the element to match styles for * @return list of matching css styles sorted by priority */ QStringList matchStyles(const KoXmlElement &element) const; private: class Private; Private * const d; }; #endif // SVGCSSHELPER_H diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index 970730f12f..35de6b9972 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1544 +1,1545 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include "kis_debug.h" #include "kis_global.h" SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); setFileFetcher( [this](const QString &name) { const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name; QFile file(fileName); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), QByteArray()); file.open(QIODevice::ReadOnly); return file.readAll(); }); } void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch) { KIS_ASSERT(!m_context.currentGC()); m_context.pushGraphicsContext(); m_context.currentGC()->pixelsPerInch = pixelsPerInch; const qreal scale = 72.0 / pixelsPerInch; const QTransform t = QTransform::fromScale(scale, scale); m_context.currentGC()->currentBoundingBox = boundsInPixels; m_context.currentGC()->matrix = t; } QList SvgParser::shapes() const { return m_shapes; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); - if (e.childNodesCount() == 0) { + if (KoXml::childNodesCount(e) == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape) { /** * Unlike the gradient parsing function, this method is called every time we * *reference* the pattern, not when we define it. Therefore we can already * use the coordinate system of the destination. */ QSharedPointer pattHelper; SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return pattHelper; const QString patternId = e.attribute("id"); if (patternId.isEmpty()) return pattHelper; pattHelper = toQShared(new KoVectorPatternBackground); if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty() &&href != patternId) { // copy the referenced pattern if found QSharedPointer pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * * TODO: Please also not that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer marker(new KoMarker()); marker->setCoordinateSystem( KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth"))); marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")), parseUnitY(e.attribute("refY")))); marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")), parseUnitY(e.attribute("markerHeight", "3")))); const QString orientation = e.attribute("orient", "0"); if (orientation == "auto") { marker->setAutoOrientation(true); } else { marker->setExplicitOrientation(parseAngular(orientation)); } // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize()); KoShape *markerShape = parseGroup(e); m_context.popGraphicsContext(); if (!markerShape) return false; marker->setShapes({markerShape}); m_markers.insert(id, QExplicitlySharedDataPointer(marker.take())); return true; } bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (KoPathShape *pathShape = dynamic_cast(shape)) { applyMarkers(pathShape); } applyFilter(shape); applyClipping(shape, shapeToOriginalUserCoordinates); applyMaskClipping(shape, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { /** * WARNING: here is a small inconsistency with the standard: * in the standard, 'display' is not inherited, but in * flake it is! * * NOTE: though the standard says: "A value of 'display:none' indicates * that the given element and ***its children*** shall not be * rendered directly". Therefore, using setVisible(false) is fully * legitimate here (DK 29.11.16). */ shape->setVisible(false); } shape->setTransparency(1.0 - gc->opacity); } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates) { applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(gradient->gradient()); QLinearGradient *g = new QLinearGradient(); g->setStart(userToRelative.map(o->start())); g->setFinalStop(userToRelative.map(o->finalStop())); g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStops(o->stops()); g->setSpread(o->spread()); resultGradient = g; *transform = relativeToUser * gradient->transform() * userToRelative; } else if (gradient->gradient()->type() == QGradient::RadialGradient) { // For radial and conical gradients such conversion is not possible resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform() * gc->matrix * shape->transformation().inverted(); } } return resultGradient; } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector dashes = srcStroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) { dashes[i] /= lineWidth; } dstStroke->setLineStyle(Qt::CustomDashLine, dashes); dstStroke->setDashOffset(dashOffset / lineWidth); } else { dstStroke->setLineStyle(Qt::SolidLine, QVector()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox tranformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e) { KoShape *result = 0; QString id = e.attribute("xlink:href"); if (!id.isEmpty()) { QString key = id.mid(1); if (m_context.hasDefinition(key)) { SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: parse 'width' and 'hight' as well gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); const KoXmlElement &referencedElement = m_context.definition(key); result = parseGroup(e, referencedElement); m_context.popGraphicsContext(); } } return result; } void SvgParser::addToGroup(QList shapes, KoShapeGroup *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not clipped, but inherit transform KoShapeGroupCommand cmd(group, shapes, false, true, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w); const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h); QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; // SVG 1.1: skip the rendering of the element if it has null viewBox if (gc->currentBoundingBox.isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * coordinateSystemOnLoading; KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate); return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy()); } KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom) { m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); // groups should also have their own coordinate system! group->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix); uploadStyleToContext(b); QList childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } QList SvgParser::parseContainer(const KoXmlElement &e) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemeted yet } } QList currentShapes = parseSingleElement(b); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } QList SvgParser::parseSingleElement(const KoXmlElement &b) { QList shapes; // save definition for later instantiation with 'use' m_context.addDefinition(b); if (b.tagName() == "svg") { shapes += parseSvg(b); } else if (b.tagName() == "g" || b.tagName() == "a" || b.tagName() == "symbol") { // treat svg link as group so we don't miss its child elements shapes += parseGroup(b); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { - if (b.childNodesCount() > 0) { + if (KoXml::childNodesCount(b) > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image" || b.tagName() == "text") { KoShape *shape = createObjectDirect(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { shapes += parseUse(b); } else if (b.tagName() == "color-profile") { m_context.parseProfile(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } } return shapes; } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QString points = element.attribute("points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); QStringList pointList = points.split(' ', QString::SkipEmptyParts); for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace((*it).toDouble())); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace((*it).toDouble())); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } KoShape * SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) shape->setShapeId(factory->id()); // reset tranformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/flake/svg/SvgShape.h b/libs/flake/svg/SvgShape.h index 70f11ad1ae..26d1d042e1 100644 --- a/libs/flake/svg/SvgShape.h +++ b/libs/flake/svg/SvgShape.h @@ -1,42 +1,42 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGSHAPE_H #define SVGSHAPE_H #include "kritaflake_export.h" class SvgSavingContext; class SvgLoadingContext; -class KoXmlElement; +#include /// An interface providing svg loading and saving routines class KRITAFLAKE_EXPORT SvgShape { public: virtual ~SvgShape(); /// Saves data utilizing specified svg saving context virtual bool saveSvg(SvgSavingContext &context); /// Loads data from specified svg element virtual bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context); }; #endif // SVGSHAPE_H diff --git a/libs/flake/svg/SvgStyleParser.h b/libs/flake/svg/SvgStyleParser.h index c5439e3294..93a0c8eb4f 100644 --- a/libs/flake/svg/SvgStyleParser.h +++ b/libs/flake/svg/SvgStyleParser.h @@ -1,78 +1,78 @@ /* This file is part of the KDE project * Copyright (C) 2002-2003,2005 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005,2007-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. */ #ifndef SVGSTYLEPARSER_H #define SVGSTYLEPARSER_H #include "kritaflake_export.h" #include #include typedef QMap SvgStyles; class SvgLoadingContext; class SvgGraphicsContext; -class KoXmlElement; +#include class QColor; class QGradient; class KRITAFLAKE_EXPORT SvgStyleParser { public: explicit SvgStyleParser(SvgLoadingContext &context); ~SvgStyleParser(); /// Parses specified style attributes void parseStyle(const SvgStyles &styles); /// Parses font attributes void parseFont(const SvgStyles &styles); /// Parses a color attribute bool parseColor(QColor &, const QString &); /// Parses gradient color stops void parseColorStops(QGradient *, const KoXmlElement &, SvgGraphicsContext *context, const QGradientStops &defaultStops); /// Creates style map from given xml element SvgStyles collectStyles(const KoXmlElement &); /// Merges two style elements, returning the merged style SvgStyles mergeStyles(const SvgStyles &, const SvgStyles &); /// Merges two style elements, returning the merged style SvgStyles mergeStyles(const KoXmlElement &, const KoXmlElement &); SvgStyles parseOneCssStyle(const QString &style, const QStringList &interestingAttributes); private: /// Parses a single style attribute void parsePA(SvgGraphicsContext *, const QString &, const QString &); /// Returns inherited attribute value for specified element QString inheritedAttribute(const QString &attributeName, const KoXmlElement &e); class Private; Private * const d; }; #endif // SVGSTYLEPARSER_H diff --git a/libs/flake/svg/SvgUtil.h b/libs/flake/svg/SvgUtil.h index 5b16cb3c25..793d76ee7d 100644 --- a/libs/flake/svg/SvgUtil.h +++ b/libs/flake/svg/SvgUtil.h @@ -1,141 +1,141 @@ /* 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. */ #ifndef SVGUTIL_H #define SVGUTIL_H #include "kritaflake_export.h" #include class QString; class SvgGraphicsContext; class QTransform; -class KoXmlElement; +#include class KRITAFLAKE_EXPORT SvgUtil { public: // remove later! pixels *are* user coordinates static double fromUserSpace(double value); static double toUserSpace(double value); static double ptToPx(SvgGraphicsContext *gc, double value); /// Converts given point from points to userspace units. static QPointF toUserSpace(const QPointF &point); /// Converts given rectangle from points to userspace units. static QRectF toUserSpace(const QRectF &rect); /// Converts given rectangle from points to userspace units. static QSizeF toUserSpace(const QSizeF &size); /** * Parses the given string containing a percentage number. * @param s the input string containing the percentage * @return the percentage number normalized to 0..100 */ static double toPercentage(QString s); /** * Parses the given string containing a percentage number. * @param s the input string containing the percentage * @return the percentage number normalized to 0..1 */ static double fromPercentage(QString s); /** * Converts position from objectBoundingBox units to userSpace units. */ static QPointF objectToUserSpace(const QPointF &position, const QRectF &objectBound); /** * Converts size from objectBoundingBox units to userSpace units. */ static QSizeF objectToUserSpace(const QSizeF &size, const QRectF &objectBound); /** * Converts position from userSpace units to objectBoundingBox units. */ static QPointF userSpaceToObject(const QPointF &position, const QRectF &objectBound); /** * Converts size from userSpace units to objectBoundingBox units. */ static QSizeF userSpaceToObject(const QSizeF &size, const QRectF &objectBound); /// Converts specified transformation to a string static QString transformToString(const QTransform &transform); /// Parses a viewbox attribute into an rectangle static bool parseViewBox(SvgGraphicsContext *gc, const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform); struct PreserveAspectRatioParser; static void parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewRect, QTransform *_viewTransform); /// Parses a length attribute static qreal parseUnit(SvgGraphicsContext *gc, const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF()); /// parses a length attribute in x-direction static qreal parseUnitX(SvgGraphicsContext *gc, const QString &unit); /// parses a length attribute in y-direction static qreal parseUnitY(SvgGraphicsContext *gc, const QString &unit); /// parses a length attribute in xy-direction static qreal parseUnitXY(SvgGraphicsContext *gc, const QString &unit); /// parses angle, result in *radians*! static qreal parseUnitAngular(SvgGraphicsContext *gc, const QString &unit); /// parses the number into parameter number static const char * parseNumber(const char *ptr, qreal &number); static qreal parseNumber(const QString &string); static QString mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element); struct PreserveAspectRatioParser { PreserveAspectRatioParser(const QString &str); enum Alignment { Min, Middle, Max }; bool defer = false; Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio; Alignment xAlignment = Min; Alignment yAlignment = Min; QPointF rectAnchorPoint(const QRectF &rc) const; QString toString() const; private: Alignment alignmentFromString(const QString &str) const; QString alignmentToString(Alignment alignment) const; static qreal alignedValue(qreal min, qreal max, Alignment alignment); }; }; #endif // SVGUTIL_H diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index a2e482dda4..d6cb78d09c 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,420 +1,420 @@ /* * 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.h" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_layer_properties_icons.h" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; KisBaseNode::Property hack_visible; //HACK QUuid id; QMap keyframeChannels; QScopedPointer opacityChannel; bool systemLocked; bool collapsed; bool supportsLodMoves; bool animated; bool useInTimeline; Private() : id(QUuid::createUuid()) , systemLocked(false) , collapsed(false) , supportsLodMoves(false) , animated(false) , useInTimeline(false) { } Private(const Private &rhs) : compositeOp(rhs.compositeOp), id(QUuid::createUuid()), systemLocked(false), collapsed(rhs.collapsed), supportsLodMoves(rhs.supportsLodMoves), animated(rhs.animated), useInTimeline(rhs.useInTimeline) { QMapIterator iter = rhs.properties.propertyIterator(); while (iter.hasNext()) { iter.next(); properties.setProperty(iter.key(), iter.value()); } } }; KisBaseNode::KisBaseNode() : m_d(new Private()) { /** * Be cautious! These two calls are vital to warm-up KoProperties. * We use it and its QMap in a threaded environment. This is not * officially suported by Qt, but our environment guarantees, that * there will be the only writer and several readers. Whilst the * value of the QMap is boolean and there are no implicit-sharing * calls provocated, it is safe to work with it in such an * environment. */ setVisible(true, true); setUserLocked(false); setCollapsed(false); setSupportsLodMoves(true); m_d->compositeOp = COMPOSITE_OVER; } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private(*rhs.m_d)) { } KisBaseNode::~KisBaseNode() { delete m_d; } quint8 KisBaseNode::opacity() const { if (m_d->opacityChannel) { qreal value = m_d->opacityChannel->currentValue(); if (!qIsNaN(value)) { return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, val); } } if (opacity() == val) return; nodeProperties().setProperty("opacity", val); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } quint8 KisBaseNode::percentOpacity() const { return int(float(opacity() * 100) / 255 + 0.5); } void KisBaseNode::setPercentOpacity(quint8 val) { setOpacity(int(float(val * 255) / 100 + 0.5)); } const QString& KisBaseNode::compositeOpId() const { return m_d->compositeOp; } void KisBaseNode::setCompositeOpId(const QString& compositeOp) { if (m_d->compositeOp == compositeOp) return; m_d->compositeOp = compositeOp; baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const { KisBaseNode::PropertyList l; l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked()); return l; } void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { setVisible(properties.at(0).state.toBool()); m_d->hack_visible = properties.at(0); setUserLocked(properties.at(1).state.toBool()); } KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } void KisBaseNode::mergeNodeProperties(const KoProperties & properties) { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } bool KisBaseNode::check(const KoProperties & properties) const { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); if (m_d->properties.contains(iter.key())) { if (m_d->properties.value(iter.key()) != iter.value()) return false; } } return true; } QImage KisBaseNode::createThumbnail(qint32 w, qint32 h) { try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (std::bad_alloc) { return QImage(); } } QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time) { Q_UNUSED(time) return createThumbnail(w, h); } bool KisBaseNode::visible(bool recursive) const { bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); KisBaseNodeSP parentNode = parentCallback(); return recursive && isVisible && parentNode ? parentNode->visible() : isVisible; } void KisBaseNode::setVisible(bool visible, bool loading) { const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); if (!loading && isVisible == visible) return; m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible); notifyParentVisibilityChanged(visible); if (!loading) { emit visibilityChanged(visible); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } void KisBaseNode::setUserLocked(bool locked) { const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true); if (isLocked == locked) return; m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked); emit userLockingChanged(locked); baseNodeChangedCallback(); } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = (visible(false) && !userLocked()); } else { editable = (!userLocked()); } if (editable) { KisBaseNodeSP parentNode = parentCallback(); if (parentNode && parentNode != this) { editable = parentNode->isEditable(checkVisibility); } } return editable; } bool KisBaseNode::hasEditablePaintDevice() const { return paintDevice() && isEditable(); } void KisBaseNode::setCollapsed(bool collapsed) { m_d->collapsed = collapsed; } bool KisBaseNode::collapsed() const { return m_d->collapsed; } void KisBaseNode::setColorLabelIndex(int index) { const int currentLabel = colorLabelIndex(); if (currentLabel == index) return; m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index); baseNodeChangedCallback(); } int KisBaseNode::colorLabelIndex() const { return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0); } QUuid KisBaseNode::uuid() const { return m_d->id; } void KisBaseNode::setUuid(const QUuid& id) { m_d->id = id; baseNodeChangedCallback(); } bool KisBaseNode::supportsLodMoves() const { return m_d->supportsLodMoves; } void KisBaseNode::setImage(KisImageWSP image) { Q_UNUSED(image); } void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } -QList KisBaseNode::keyframeChannels() const +QMap KisBaseNode::keyframeChannels() const { - return m_d->keyframeChannels.values(); + return m_d->keyframeChannels; } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } bool KisBaseNode::useInTimeline() const { return m_d->useInTimeline; } void KisBaseNode::setUseInTimeline(bool value) { if (value == m_d->useInTimeline) return; m_d->useInTimeline = value; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); emit keyframeChannelAdded(channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, device->defaultBounds(), KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h index 5ee2b50ddc..dd795caaec 100644 --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -1,565 +1,565 @@ /* * 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_BASE_NODE_H #define _KIS_BASE_NODE_H #include #include #include #include #include #include "kis_shared.h" #include "kis_paint_device.h" #include "kis_processing_visitor.h" // included, not forward declared for msvc class KoProperties; class KoColorSpace; class KoCompositeOp; class KisNodeVisitor; class KisUndoAdapter; class KisKeyframeChannel; #include "kritaimage_export.h" /** * A KisBaseNode is the base class for all components of an image: * nodes, layers masks, selections. A node has a number of properties, * can be represented as a thumbnail and knows what to do when it gets * a certain paint device to process. A KisBaseNode does not know * anything about its peers. You should not directly inherit from a * KisBaseNode; inherit from KisNode instead. */ class KRITAIMAGE_EXPORT KisBaseNode : public QObject, public KisShared { Q_OBJECT public: /** * Describes a property of a document section. * * FIXME: using a QList instead of QMap and not having an untranslated identifier, * either enum or string, forces applications to rely on the order of properties * or to compare the translated strings. This makes it hard to robustly extend the * properties of document section items. */ struct Property { QString id; /** i18n-ed name, suitable for displaying */ QString name; /** Whether the property is a boolean (e.g. locked, visible) which can be toggled directly from the widget itself. */ bool isMutable; /** Provide these if the property isMutable. */ QIcon onIcon; QIcon offIcon; /** If the property isMutable, provide a boolean. Otherwise, a string suitable for displaying. */ QVariant state; /** If the property is mutable, specifies whether it can be put into stasis. When a property is in stasis, a new state is created, and the old one is stored in stateInStasis. When stasis ends, the old value is restored and the new one discarded */ bool canHaveStasis; /** If the property isMutable and canHaveStasis, indicate whether it is in stasis or not */ bool isInStasis; /** If the property isMutable and canHaveStasis, provide this value to store the property's state while in stasis */ bool stateInStasis; bool operator==(const Property &rhs) const { return rhs.name == name; } Property(): isMutable( false ) { } /// Constructor for a mutable property. Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn ) : id(n.id()), name( n.name() ), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( false ) { } /** Constructor for a mutable property accepting stasis */ Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn, bool _isInStasis, bool _stateInStasis ) : id(n.id()), name(n.name()), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( true ), isInStasis( _isInStasis ), stateInStasis( _stateInStasis ) { } /// Constructor for a nonmutable property. Property( const KoID &n, const QString &s ) : id(n.id()), name(n.name()), isMutable( false ), state( s ) { } }; /** Return this type for PropertiesRole. */ typedef QList PropertyList; public: /** * Create a new, empty base node. The node is unnamed, unlocked * visible and unlinked. */ KisBaseNode(); /** * Create a copy of this node. */ KisBaseNode(const KisBaseNode & rhs); /** * Delete this node */ ~KisBaseNode() override; /** * Return the paintdevice you can use to change pixels on. For a * paint layer these will be paint pixels, for an adjustment layer or a mask * the selection paint device. * * @return the paint device to paint on. Can be 0 if the actual * node type does not support painting. */ virtual KisPaintDeviceSP paintDevice() const = 0; /** * @return the rendered representation of a node * before the effect masks have had their go at it. Can be 0. */ virtual KisPaintDeviceSP original() const = 0; /** * @return the fully rendered representation of this layer: its * rendered original and its effect masks. Can be 0. */ virtual KisPaintDeviceSP projection() const = 0; virtual const KoColorSpace *colorSpace() const = 0; /** * Return the opacity of this layer, scaled to a range between 0 * and 255. * XXX: Allow true float opacity */ quint8 opacity() const; //0-255 /** * Set the opacity for this layer. The range is between 0 and 255. * The layer will be marked dirty. * * XXX: Allow true float opacity */ void setOpacity(quint8 val); //0-255 /** * return the 8-bit opacity of this layer scaled to the range * 0-100 * * XXX: Allow true float opacity */ quint8 percentOpacity() const; //0-100 /** * Set the opacity of this layer with a number between 0 and 100; * the number will be scaled to between 0 and 255. * XXX: Allow true float opacity */ void setPercentOpacity(quint8 val); //0-100 /** * Return the composite op associated with this layer. */ virtual const KoCompositeOp *compositeOp() const = 0; const QString& compositeOpId() const; /** * Set a new composite op for this layer. The layer will be marked * dirty. */ void setCompositeOpId(const QString& compositeOpId); /** * @return unique id, which is now used by clone layers. */ QUuid uuid() const; /** * Set the uuid of node. This should only be used when loading * existing node and in constructor. */ void setUuid(const QUuid& id); /** * return the name of this node. This is the same as the * QObject::objectName. */ QString name() const { return objectName(); } /** * set the QObject::objectName. This is also the user-visible name * of the layer. The reason for this is that we want to see the * layer name also when debugging. */ void setName(const QString& name) { setObjectName(name); baseNodeChangedCallback(); } /** * @return the icon used to represent the node type, for instance * in the layerbox and in the menu. */ virtual QIcon icon() const { return QIcon(); } /** * Return a the properties of this base node (locked, visible etc, * with the right icons for their representation and their state. * * Subclasses can extend this list with new properties, like * opacity for layers or visualized for masks. * * The order of properties is, unfortunately, for now, important, * so take care which properties superclasses of your class * define. * * KisBaseNode defines visible = 0, locked = 1 * KisLayer defines opacity = 2, compositeOp = 3 * KisMask defines active = 2 (KisMask does not inherit kislayer) */ virtual PropertyList sectionModelProperties() const; /** * Change the section model properties. */ virtual void setSectionModelProperties(const PropertyList &properties); /** * Return all the properties of this layer as a KoProperties-based * serializable key-value list. */ KoProperties & nodeProperties() const; /** * Merge the specified properties with the properties of this * layer. Whereever these properties overlap, the value of the * node properties is changed. No properties on the node are * deleted. If there are new properties in this list, they will be * added on the node. */ void mergeNodeProperties(const KoProperties & properties); /** * Compare the given properties list with the properties of this * node. * * @return false only if the same property exists in both lists * but with a different value. Properties that are not in both * lists are disregarded. */ bool check(const KoProperties & properties) const; /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisNodeVisitor for this * node type, so you need to override it for all leaf classes in * the node inheritance hierarchy. * * return false if the visitor could not successfully act on this * node instance. */ virtual bool accept(KisNodeVisitor &) { return false; } /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisProcessingVisitor * for this node type, so you need to override it for all leaf * classes in the node inheritance hierarchy. * * The processing visitor differs from node visitor in the way * that it accepts undo adapter, that allows the processing to * be multithreaded */ virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { Q_UNUSED(visitor); Q_UNUSED(undoAdapter); } /** * @return a thumbnail in requested size. The thumbnail is a rgba * QImage and may have transparent parts. Returns a fully * transparent QImage of the requested size if the current node * type cannot generate a thumbnail. If the requested size is too * big, return a null QImage. */ virtual QImage createThumbnail(qint32 w, qint32 h); /** * @return a thumbnail in requested size for the defined timestamp. * The thumbnail is a rgba Image and may have transparent parts. * Returns a fully transparent QImage of the requested size if the * current node type cannot generate a thumbnail. If the requested * size is too big, return a null QImage. */ virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time); /** * Ask this node to re-read the pertinent settings from the krita * configuration. */ virtual void updateSettings() { } /** * @return true if this node is visible (i.e, active (except for * selection masks where visible and active properties are * different)) in the graph */ virtual bool visible(bool recursive = false) const; /** * Set the visible status of this node. Visible nodes are active * in the graph (except for selections masks which can be active * while hidden), that is to say, they are taken into account * when merging. Invisible nodes play no role in the final image *, but will be modified when modifying all layers, for instance * when cropping. * * Toggling the visibility of a node will not automatically lead * to recomposition. * * @param visible the new visibility state * @param isLoading if true, the property is set during loading. */ virtual void setVisible(bool visibile, bool loading = false); /** * Return the locked status of this node. Locked nodes cannot be * edited. */ bool userLocked() const; /** * Set the locked status of this node. Locked nodes cannot be * edited. */ void setUserLocked(bool l); /** * @return true if the node can be edited: * * if checkVisibility is true, then the node is only editable if it is visible and not locked. * if checkVisibility is false, then the node is editable if it's not locked. */ bool isEditable(bool checkVisibility = true) const; /** * @return true if the node is editable and has a paintDevice() * which which can be used for accessing pixels. It is an * equivalent to (isEditable() && paintDevice()) */ bool hasEditablePaintDevice() const; /** * @return the x-offset of this layer in the image plane. */ virtual qint32 x() const { return 0; } /** * Set the x offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setX(qint32) { } /** * @return the y-offset of this layer in the image plane. */ virtual qint32 y() const { return 0; } /** * Set the y offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setY(qint32) { } /** * Returns an approximation of where the bounds on actual data are * in this node. */ virtual QRect extent() const { return QRect(); } /** * Returns the exact bounds of where the actual data resides in * this node. */ virtual QRect exactBounds() const { return QRect(); } /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ void setColorLabelIndex(int index); /** * \see setColorLabelIndex */ int colorLabelIndex() const; /** * Returns true if the offset of the node can be changed in a LodN * stroke. Currently, all the nodes except shape layers support that. */ bool supportsLodMoves() const; /** * Return the keyframe channels associated with this node * @return list of keyframe channels */ - QList keyframeChannels() const; + QMap keyframeChannels() const; /** * Get the keyframe channel with given id. * If the channel does not yet exist and the node supports the requested * channel, it will be created if create is true. * @param id internal name for channel * @param create attempt to create the channel if it does not exist yet * @return keyframe channel with the id, or null if not found */ KisKeyframeChannel *getKeyframeChannel(const QString &id, bool create); KisKeyframeChannel *getKeyframeChannel(const QString &id) const; bool useInTimeline() const; void setUseInTimeline(bool value); bool isAnimated() const; virtual void enableAnimation(); virtual void setImage(KisImageWSP image); protected: void setSupportsLodMoves(bool value); /** * FIXME: This method is a workaround for getting parent node * on a level of KisBaseNode. In fact, KisBaseNode should inherit * KisNode (in terms of current Krita) to be able to traverse * the node stack */ virtual KisBaseNodeSP parentCallback() const { return KisBaseNodeSP(); } virtual void notifyParentVisibilityChanged(bool value) { Q_UNUSED(value); } /** * This callback is called when some meta state of the base node * that can be interesting to the UI has changed. E.g. visibility, * lockness, opacity, compositeOp and etc. This signal is * forwarded by the KisNode and KisNodeGraphListener to the model * in KisLayerBox, so it can update its controls when information * changes. */ virtual void baseNodeChangedCallback() { } virtual void baseNodeInvalidateAllFramesCallback() { } /** * Add a keyframe channel for this node. The channel will be added * to the common hash table which will be available to the UI. * * WARNING: the \p channel object *NOT* become owned by the node! * The caller must ensure manually that the lifetime of * the object coincide with the lifetime of the node. */ virtual void addKeyframeChannel(KisKeyframeChannel* channel); /** * Attempt to create the requested channel. Used internally by getKeyframeChannel. * Subclasses should implement this method to catch any new channel types they support. * @param id channel to create * @return newly created channel or null */ virtual KisKeyframeChannel * requestKeyframeChannel(const QString &id); Q_SIGNALS: /** * This signal is emitted when the visibility of the layer is changed with \ref setVisible. */ void visibilityChanged(bool); /** * This signal is emitted when the node is locked or unlocked with \ref setUserLocked. */ void userLockingChanged(bool); void keyframeChannelAdded(KisKeyframeChannel *channel); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE( KisBaseNode::PropertyList ) #endif diff --git a/libs/image/kis_filter_strategy.cc b/libs/image/kis_filter_strategy.cc index 06dc41e39b..5dc92e064a 100644 --- a/libs/image/kis_filter_strategy.cc +++ b/libs/image/kis_filter_strategy.cc @@ -1,238 +1,238 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2005 C. Boemann * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_filter_strategy.h" #include #include #include #include "kis_debug.h" Q_GLOBAL_STATIC(KisFilterStrategyRegistry, s_instance) qreal KisHermiteFilterStrategy::valueAt(qreal t) const { /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ if (t < 0.0) t = -t; if (t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0); return(0.0); } qint32 KisHermiteFilterStrategy::intValueAt(qint32 t) const { /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ if (t < 0) t = -t; if (t < 256) { t = (2 * t - 3 * 256) * t * t + (256 << 16); //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) t = (t + 0x8000) >> 16; // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; if (t >= 128) return t - 1; return t; } return(0); } qint32 KisBicubicFilterStrategy::intValueAt(qint32 t) const { /* f(t) = 1.5|t|^3 - 2.5|t|^2 + 1, -1 <= t <= 1 */ if (t < 0) t = -t; if (t < 256) { t = (3 * t - 5 * 256) * t * t / 2 + (256 << 16); //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) t = (t + 0x8000) >> 16; // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; if (t >= 128) return t - 1; return t; } if (t < 512) { /* f(t) = -0.5|t|^3 + 2.5|t|^2 + 4|t| - 2, -2 <= t <= 2 */ t = ((-t + 5 * 256) * t / 2 - 4 * 256 * 256) * t + (2 * 256 << 16); //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) t = (t + 0x8000) >> 16; // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; if (t >= 128) return t - 1; return t; } return(0); } qreal KisBoxFilterStrategy::valueAt(qreal t) const { if ((t > -0.5) && (t <= 0.5)) return(1.0); return(0.0); } qint32 KisBoxFilterStrategy::intValueAt(qint32 t) const { /* f(t) = 1, -0.5 < t <= 0.5 */ if ((t > -128) && (t <= 128)) return 255; return 0; } qreal KisBilinearFilterStrategy::valueAt(qreal t) const { if (t < 0.0) t = -t; if (t < 1.0) return(1.0 - t); return(0.0); } qint32 KisBilinearFilterStrategy::intValueAt(qint32 t) const { /* f(t) = |t|, -1 <= t <= 1 */ if (t < 0) t = -t; if (t < 256) { // calc 256-1 but also go from .8 fixed point to 8bitscale. ie t = (t*255)/256; ie: if(t>=128) return t-1; if (t >= 128) return 256 - t; return 255 - t; } return(0); } qreal KisBellFilterStrategy::valueAt(qreal t) const { if (t < 0) t = -t; if (t < .5) return(.75 - (t * t)); if (t < 1.5) { t = (t - 1.5); return(.5 *(t * t)); } return(0.0); } qreal KisBSplineFilterStrategy::valueAt(qreal t) const { qreal tt; if (t < 0) t = -t; if (t < 1) { tt = t * t; return((.5 * tt * t) - tt + (2.0 / 3.0)); } else if (t < 2) { t = 2 - t; return((1.0 / 6.0) *(t * t * t)); } return(0.0); } qreal KisLanczos3FilterStrategy::valueAt(qreal t) const { if (t < 0) t = -t; if (t < 3.0) return(sinc(t) * sinc(t / 3.0)); return(0.0); } qreal KisLanczos3FilterStrategy::sinc(qreal x) const { const qreal pi = 3.1415926535897932385; x *= pi; if (x != 0) return(sin(x) / x); return(1.0); } qreal KisMitchellFilterStrategy::valueAt(qreal t) const { const qreal B = 1.0 / 3.0; const qreal C = 1.0 / 3.0; qreal tt; tt = t * t; if (t < 0) t = -t; if (t < 1.0) { t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt)) + ((-18.0 + 12.0 * B + 6.0 * C) * tt) + (6.0 - 2 * B)); return(t / 6.0); } else if (t < 2.0) { t = (((-1.0 * B - 6.0 * C) * (t * tt)) + ((6.0 * B + 30.0 * C) * tt) + ((-12.0 * B - 48.0 * C) * t) + (8.0 * B + 24 * C)); return(t / 6.0); } return(0.0); } KisFilterStrategyRegistry::KisFilterStrategyRegistry() { } KisFilterStrategyRegistry::~KisFilterStrategyRegistry() { Q_FOREACH (const QString &id, keys()) { delete get(id); - } + } dbgRegistry << "deleting KisFilterStrategyRegistry"; } KisFilterStrategyRegistry* KisFilterStrategyRegistry::instance() { if (!s_instance.exists()) { - // s_instance->add(new KisHermiteFilterStrategy); + s_instance->add(new KisHermiteFilterStrategy); s_instance->add(new KisBicubicFilterStrategy); s_instance->add(new KisBoxFilterStrategy); s_instance->add(new KisBilinearFilterStrategy); - // s_instance->add(new KisBellFilterStrategy); - // s_instance->add(new KisBSplineFilterStrategy); + s_instance->add(new KisBellFilterStrategy); + s_instance->add(new KisBSplineFilterStrategy); s_instance->add(new KisLanczos3FilterStrategy); - // s_instance->add(new KisMitchellFilterStrategy); + s_instance->add(new KisMitchellFilterStrategy); } return s_instance; } QList KisFilterStrategyRegistry::listKeys() const { QList answer; Q_FOREACH (const QString key, keys()) { answer.append(KoID(key, get(key)->name())); } return answer; } -QString KisFilterStrategyRegistry::formatedDescriptions() const +QString KisFilterStrategyRegistry::formattedDescriptions() const { QString formatedDescription(""); Q_FOREACH (const QString key, keys()) { KisFilterStrategy *strategy = get(key); QString description = strategy->description(); if (!description.isEmpty()) { formatedDescription.append("

"); formatedDescription.append(strategy->name()); formatedDescription.append(": "); formatedDescription.append(description); formatedDescription.append("

"); } } formatedDescription.append(""); return formatedDescription; } diff --git a/libs/image/kis_filter_strategy.h b/libs/image/kis_filter_strategy.h index 1cd5e36daf..68ea3029ad 100644 --- a/libs/image/kis_filter_strategy.h +++ b/libs/image/kis_filter_strategy.h @@ -1,206 +1,206 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2005 C. Boemann * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_FILTER_STRATEGY_H_ #define KIS_FILTER_STRATEGY_H_ #include #include "KoGenericRegistry.h" #include "KoID.h" #include "kritaimage_export.h" class KRITAIMAGE_EXPORT KisFilterStrategy { public: KisFilterStrategy(KoID id) : m_id(id) {} - virtual ~KisFilterStrategy() {} + virtual ~KisFilterStrategy() { } QString id() { return m_id.id(); } QString name() { return m_id.name(); } virtual qreal valueAt(qreal /*t*/) const { return 0; } virtual qint32 intValueAt(qint32 t) const { return qint32(255*valueAt(t / 256.0)); } qreal support() { return supportVal; } qint32 intSupport() { return intSupportVal; } virtual bool boxSpecial() { return false; } virtual QString description() { return QString(); } protected: qreal supportVal; qint32 intSupportVal; KoID m_id; }; class KRITAIMAGE_EXPORT KisHermiteFilterStrategy : public KisFilterStrategy { public: KisHermiteFilterStrategy() : KisFilterStrategy(KoID("Hermite", i18n("Hermite"))) { supportVal = 1.0; intSupportVal = 256; } ~KisHermiteFilterStrategy() override {} qint32 intValueAt(qint32 t) const override; qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisBicubicFilterStrategy : public KisFilterStrategy { public: KisBicubicFilterStrategy() : KisFilterStrategy(KoID("Bicubic", i18n("Bicubic"))) { supportVal = 2.0; intSupportVal = 512; } ~KisBicubicFilterStrategy() override {} QString description() override { return i18n("Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear."); } qint32 intValueAt(qint32 t) const override; }; class KRITAIMAGE_EXPORT KisBoxFilterStrategy : public KisFilterStrategy { public: KisBoxFilterStrategy() : KisFilterStrategy(KoID("Box", i18n("Box"))) { supportVal = 0.5; intSupportVal = 128; } ~KisBoxFilterStrategy() override {} QString description() override { return i18n("Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects."); } qint32 intValueAt(qint32 t) const override; qreal valueAt(qreal t) const override; bool boxSpecial() override { return true; } }; class KRITAIMAGE_EXPORT KisBilinearFilterStrategy : public KisFilterStrategy { public: KisBilinearFilterStrategy() : KisFilterStrategy(KoID("Bilinear", i18n("Bilinear"))) { supportVal = 1.0; intSupportVal = 256; } ~KisBilinearFilterStrategy() override {} QString description() override { return i18n("Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size."); } qint32 intValueAt(qint32 t) const override; qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisBellFilterStrategy : public KisFilterStrategy { public: KisBellFilterStrategy() : KisFilterStrategy(KoID("Bell", i18n("Bell"))) { supportVal = 1.5; intSupportVal = 128 + 256; } ~KisBellFilterStrategy() override {} qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisBSplineFilterStrategy : public KisFilterStrategy { public: KisBSplineFilterStrategy() : KisFilterStrategy(KoID("BSpline", i18n("BSpline"))) { supportVal = 2.0; intSupportVal = 512; } ~KisBSplineFilterStrategy() override {} qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisLanczos3FilterStrategy : public KisFilterStrategy { public: KisLanczos3FilterStrategy() : KisFilterStrategy(KoID("Lanczos3", i18n("Lanczos3"))) { supportVal = 3.0; intSupportVal = 768; } ~KisLanczos3FilterStrategy() override {} QString description() override { return i18n("Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges."); } qreal valueAt(qreal t) const override; private: qreal sinc(qreal x) const; }; class KRITAIMAGE_EXPORT KisMitchellFilterStrategy : public KisFilterStrategy { public: KisMitchellFilterStrategy() : KisFilterStrategy(KoID("Mitchell", i18n("Mitchell"))) { supportVal = 2.0; intSupportVal = 256; } ~KisMitchellFilterStrategy() override {} qreal valueAt(qreal t) const override; }; class KRITAIMAGE_EXPORT KisFilterStrategyRegistry : public KoGenericRegistry { public: KisFilterStrategyRegistry(); ~KisFilterStrategyRegistry() override; static KisFilterStrategyRegistry* instance(); /** * 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; /** * This function return a string formated in HTML that contains the descriptions of all objects * (with a non empty description) stored in the registry. */ - QString formatedDescriptions() const; + QString formattedDescriptions() const; private: KisFilterStrategyRegistry(const KisFilterStrategyRegistry&); KisFilterStrategyRegistry operator=(const KisFilterStrategyRegistry&); }; #endif // KIS_FILTER_STRATEGY_H_ diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index a6aa2ab531..6ec76f9580 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1344 +1,1346 @@ /* * 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 "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 "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 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; virtual KisNodeList allSrcNodes() = 0; virtual KisLayerSP dstLayer() { return 0; } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } KisLayerSP dstLayer() override { return qobject_cast(dstNode.data()); } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); } } KisNodeList mergedNodes; 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 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()) { recursiveApplyNodes(node, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } } 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 decendant) 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); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { - CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) + 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; } - + m_info->dstNode = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (compositeOpId.isEmpty()) { compositeOpId = node->compositeOpId(); } else if (compositeOpId != node->compositeOpId()) { 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); } } } 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 decendant) 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)); } reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); safeRemoveMultipleNodes(m_info->allSrcNodes(), m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { foreach (KisSelectionMaskSP mask, selectionMasks) { - addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); - addCommand(new KisActivateSelectionMaskCommand(mask, false)); + if (mask) { + 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()); channel->addKeyframe(m_frame, cmd); addCommand(cmd); } 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); // 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)) { qDebug() << "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()) { 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, + 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; qSwap(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); 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; }); } } diff --git a/libs/image/kis_time_range.cpp b/libs/image/kis_time_range.cpp index 9f44a16117..85c918e961 100644 --- a/libs/image/kis_time_range.cpp +++ b/libs/image/kis_time_range.cpp @@ -1,103 +1,100 @@ /* * 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" 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) { if (!node->visible()) return; - - const QList channels = node->keyframeChannels(); - - Q_FOREACH (const KisKeyframeChannel *channel, channels) { + Q_FOREACH (const KisKeyframeChannel *channel, node->keyframeChannels()) { if (exclusive) { // Intersection range &= channel->identicalFrames(time); } else { // Union range |= channel->affectedFrames(time); } } KisNodeSP child = node->firstChild(); while (child) { calculateTimeRangeRecursive(child, time, range, exclusive); child = child->nextSibling(); } } 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/libkis/Document.cpp b/libs/libkis/Document.cpp index 2f0a935bb9..f7e5f97041 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,517 +1,549 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Document.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 struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { return new Node(d->document->image(), activeNodes.first()); } return new Node(d->document->image(), d->document->image()->root()->firstChild()); } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); return new Node(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString::null; return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(value, d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes(); } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes(); } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->deleteLater(); } } d->document->deleteLater(); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; QString mimeType = KisMimeDatabase::mimeTypeForFile(filename); d->document->setOutputMimeType(mimeType.toLatin1()); return d->document->exportDocument(QUrl::fromLocalFile(filename), exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(); } void Document::resizeImage(int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } +void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) +{ + if (!d->document) return; + KisImageSP image = d->document->image(); + if (!image) return; + QRect rc = image->bounds(); + rc.setWidth(w); + rc.setHeight(h); + + KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); + if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + + image->scaleImage(rc.size(), xres, yres, actualStrategy); +} + +void Document::rotateImage(double radians) +{ + if (!d->document) return; + KisImageSP image = d->document->image(); + if (!image) return; + image->rotateImage(radians); +} + +void Document::shearImage(double angleX, double angleY) +{ + if (!d->document) return; + KisImageSP image = d->document->image(); + if (!image) return; + image->shear(angleX, angleY); +} + bool Document::save() { if (!d->document) return false; return d->document->save(); } bool Document::saveAs(const QString &filename) { if (!d->document) return false; return d->document->saveAs(QUrl::fromLocalFile(filename)); } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType == "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QPointer Document::document() const { return d->document; } diff --git a/libs/libkis/Document.h b/libs/libkis/Document.h index 54d365df8d..e17a52db01 100644 --- a/libs/libkis/Document.h +++ b/libs/libkis/Document.h @@ -1,478 +1,514 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_DOCUMENT_H #define LIBKIS_DOCUMENT_H #include #include "kritalibkis_export.h" #include "libkis.h" class KisDocument; /** * The Document class encapsulates a Krita Document/Image. A Krita document is an Image with * a filename. Libkis does not differentiate between a document and an image, like Krita does * internally. */ class KRITALIBKIS_EXPORT Document : public QObject { Q_OBJECT Q_DISABLE_COPY(Document) public: explicit Document(KisDocument *document, QObject *parent = 0); ~Document() override; bool operator==(const Document &other) const; bool operator!=(const Document &other) const; public Q_SLOTS: /** * Batchmode means that no actions on the document should show dialogs or popups. * @return true if the document is in batchmode. */ bool batchmode() const; /** * Set batchmode to @param value. If batchmode is true, then there should be no popups * or dialogs shown to the user. */ void setBatchmode(bool value); /** * @brief activeNode retrieve the node that is currently active in the currently active window * @return the active node. If there is no active window, the first child node is returned. */ Node* activeNode() const; /** * @brief setActiveNode make the given node active in the currently active view and window * @param value the node to make active. */ void setActiveNode(Node* value); /** * @brief toplevelNodes return a list with all top level nodes in the image graph */ QList topLevelNodes() const; /** * @brief nodeByName searches the node tree for a node with the given name and returns it * @param name the name of the node * @return the first node with the given name or 0 if no node is found */ Node *nodeByName(const QString &name) const; /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return false if the colorProfile name does not correspond to to a registered profile or if assigning * the profile failed. */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is * done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint * compensation. * * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. * @return false the combination of these arguments does not correspond to a colorspace. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief documentInfo creates and XML document representing document and author information. * @return a string containing a valid XML document with the right information about the document * and author. The DTD can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ * * @code * * * * * My Document * * * * * Unknown * 1 * 35 * 2017-02-27T20:15:09 * 2017-02-27T20:14:33 * * * * Boudewijn Rempt * * * * * * * * * * * * * * * @endcode * */ QString documentInfo() const; /** * @brief setDocumentInfo set the Document information to the information contained in document * @param document A string containing a valid XML document that conforms to the document-info DTD * that can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ */ void setDocumentInfo(const QString &document); /** * @return the full path to the document, if it has been set. */ QString fileName() const; /** * @brief setFileName set the full path of the document to @param value */ void setFileName(QString value); /** * @return the height of the image in pixels */ int height() const; /** * @brief setHeight resize the document to @param value height. This is a canvas resize, not a scale. */ void setHeight(int value); /** * @return the name of the document. This is the title field in the @see documentInfo */ QString name() const; /** * @brief setName sets the name of the document to @param value. This is the title field in the @see documentInfo */ void setName(QString value); /** * @return the resolution in pixels per inch */ int resolution() const; /** * @brief setResolution set the resolution of the image; this does not scale the image * @param value the resolution in pixels per inch */ void setResolution(int value); /** * @brief rootNode the root node is the invisible group layer that contains the entire node * hierarchy. * @return the root of the image */ Node* rootNode() const; /** * @brief selection Create a Selection object around the global selection, if there is one. * @return the global selection or None if there is no global selection. */ Selection* selection() const; /** * @brief setSelection set or replace the global selection * @param value a valid selection object. */ void setSelection(Selection* value); /** * @return the width of the image in pixels. */ int width() const; /** * @brief setWidth resize the document to @param value width. This is a canvas resize, not a scale. */ void setWidth(int value); /** * @return xRes the horizontal resolution of the image in pixels per pt (there are 72 pts to an inch) */ double xRes() const; /** * @brief setXRes set the horizontal resolution of the image to xRes in pixels per pt. (there are 72 pts to an inch) */ void setXRes(double xRes) const; /** * @return yRes the vertical resolution of the image in pixels per pt (there are 72 pts to an inch) */ double yRes() const; /** * @brief setYRes set the vertical resolution of the image to yRes in pixels per pt. (there are 72 pts to an inch) */ void setYRes(double yRes) const; /** * @brief pixelData reads the given rectangle from the image projection and returns it as a byte * array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the image boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original image data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief close Close the document: remove it from Krita's internal list of documents and * close all views. If the document is modified, you should save it first. There will be * no prompt for saving. * @return true if the document is closed. */ bool close(); /** * @brief crop the image to rectangle described by @param x, @param y, * @param w and @param h */ void crop(int x, int y, int w, int h); /** * @brief exportImage export the image, without changing its URL to the given path. * @param filename the full path to which the image is to be saved * @param exportConfiguration a configuration object appropriate to the file format * @return true if the export succeeded, false if it failed. */ bool exportImage(const QString &filename, const InfoObject &exportConfiguration); /** * @brief flatten all layers in the image */ void flatten(); /** - * @brief resizeImage resize the image to the given width and height. + * @brief resizeImage resizes the canvas to the given width and height. + * Note: This doesn't scale, use scale image for that. * @param w the new width * @param h the new height */ void resizeImage(int w, int h); + /** + * @brief scaleImage + * @param w the new width + * @param h the new height + * @param xres the new xres + * @param yres the new yres + * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. + * The list of filters is extensible and can be retrieved with Krita::filter + *
    + *
  • Hermite
  • + *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • + *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • + *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • + *
  • Bell
  • + *
  • BSpline
  • + *
  • Kanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • + *
  • Mitchell
  • + *
+ */ + void scaleImage(int w, int h, int xres, int yres, QString strategy); + + /** + * @brief rotateImage + * Rotate the image by the given radians. + * @param radians the amount you wish to rotate the image in radians + */ + void rotateImage(double radians); + + /** + * @brief shearImage shear the whole image. + * @param angleX the X-angle in degrees to shear by + * @param angleY the Y-angle in degrees to shear by + */ + void shearImage(double angleX, double angleY); + /** * @brief save the image to its currently set path. The modified flag of the * document will be reset * @return true if saving succeeded, false otherwise. */ bool save(); /** * @brief saveAs save the document under the @param filename. The document's * filename will be reset to @param filename. * @param filename the new filename (full path) for the document * @return true if saving succeeded, false otherwise. */ bool saveAs(const QString &filename); /** * @brief createNode create a new node of the given type. The node is not added * to the node hierarchy; you need to do that by finding the right parent node, * getting its list of child nodes and adding the node in the right place, then * calling Node::SetChildNodes * * @param name The name of the node * * @param nodeType The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
* * When relevant, the new Node will have the colorspace of the image by default; * that can be changed with Node::setColorSpace. * * The settings and selections for relevant layer and mask types can also be set * after the Node has been created. * * @return the new Node. */ Node* createNode(const QString &name, const QString &nodeType); /** * @brief projection creates a QImage from the rendered image or * a cutout rectangle. */ QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; /** * @brief thumbnail create a thumbnail of the given dimensions. * * If the requested size is too big a null QImage is created. * * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h) const; /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void lock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void unlock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void waitForDone(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ bool tryBarrierLock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ bool isIdle(); /** * Starts a synchronous recomposition of the projection: everything will * wait until the image is fully recomputed. */ void refreshProjection(); private: friend class Krita; friend class Window; friend class Filter; QPointer document() const; private: struct Private; Private *const d; }; #endif // LIBKIS_DOCUMENT_H diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index 0275aeb97e..9012f5f1a7 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,377 +1,383 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Krita.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 "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { QList actionList; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return actionList; } KActionCollection *actionCollection = mainWindow->actionCollection(); Q_FOREACH(QAction *action, actionCollection->actions()) { actionList << new Action(action->objectName(), action); } return actionList; } Action *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); if (action) { return new Action(name, action); } return 0; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); return new Document(document); } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); qSort(ls); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } +QStringList Krita::filterStrategies() const +{ + return KisFilterStrategyRegistry::instance()->keys(); +} + QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } return profileNames.toList(); } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources = QMap (); if (type == "pattern") { KoResourceServer* server = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "gradient") { KoResourceServer* server = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "brush") { KisBrushResourceServer* server = KisBrushServer::instance()->brushServer(); Q_FOREACH (KisBrushSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type == "preset") { KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (KisPaintOpPresetSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type == "palette") { KoResourceServer* server = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "workspace") { KoResourceServer< KisWorkspaceResource >* server = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } return resources; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, true, 1, "", 100.0)) { qDebug() << "Could not create a new image"; return 0; } Q_ASSERT(document->image()); qDebug() << document->image()->objectName(); return new Document(document); } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES); return new Document(document); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } Action *Krita::createAction(const QString &text) { KisAction *action = new KisAction(text, this); KisPart::instance()->addScriptAction(action); return new Action(action->objectName(), action); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } diff --git a/libs/libkis/Krita.h b/libs/libkis/Krita.h index 183e3d08ce..beefe0f47f 100644 --- a/libs/libkis/Krita.h +++ b/libs/libkis/Krita.h @@ -1,293 +1,301 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_KRITA_H #define LIBKIS_KRITA_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "Extension.h" #include "Document.h" #include "Window.h" #include "View.h" #include "Action.h" #include "Notifier.h" class QAction; /** * Krita is a singleton class that offers the root access to the Krita object hierarchy. * * The Krita.instance() is aliased as two builtins: Scripter and Application. */ class KRITALIBKIS_EXPORT Krita : public QObject { Q_OBJECT public: explicit Krita(QObject *parent = 0); ~Krita() override; public Q_SLOTS: /** * @return the currently active document, if there is one. */ Document* activeDocument() const; /** * @brief setActiveDocument activates the first view that shows the given document * @param value the document we want to activate */ void setActiveDocument(Document* value); /** * @brief batchmode determines whether the script is run in batch mode. If batchmode * is true, scripts should now show messageboxes or dialog boxes. * * Note that this separate from Document.setBatchmode(), which determines whether * export/save option dialogs are shown. * * @return true if the script is run in batchmode */ bool batchmode() const; /** * @brief setBatchmode sets the the batchmode to @param value; if true, scripts should * not show dialogs or messageboxes. */ void setBatchmode(bool value); /** * @return return a list of all actions for the currently active mainWindow. */ QList actions() const; /** * @return the action that has been registered under the given name, or 0 if no such action exists. */ Action *action(const QString &name) const; /** * @return a list of all open Documents */ QList documents() const; /** * @brief Filters are identified by an internal name. This function returns a list * of all existing registered filters. * @return a list of all registered filters */ QStringList filters() const; /** * @brief filter construct a Filter object with a default configuration. * @param name the name of the filter. Use Krita.instance().filters() to get * a list of all possible filters. * @return the filter or None if there is no such filter. */ Filter *filter(const QString &name) const; + /** + * @brief filterStrategies Retrieves all installed filter strategies. A filter + * strategy is used when transforming (scaling, shearing, rotating) an image to + * calculate the value of the new pixels. You can use th + * @return the id's of all available filters. + */ + QStringList filterStrategies() const; + /** * @brief profiles creates a list with the names of all color profiles compatible * with the given color model and color depth. * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return a list with valid names */ QStringList profiles(const QString &colorModel, const QString &colorDepth) const; /** * @brief addProfile load the given profile into the profile registry. * @param profilePath the path to the profile. * @return true if adding the profile succeeded. */ bool addProfile(const QString &profilePath); /** * @brief notifier the Notifier singleton emits signals when documents are opened and * closed, the configuration changes, views are opened and closed or windows are opened. * @return the notifier object */ Notifier* notifier() const; /** * @brief version Determine the version of Krita * * Usage: print(Application.version ()) * * @return the version string including git sha1 if Krita was built from git */ QString version() const; /** * @return a list of all views. A Document can be shown in more than one view. */ QList views() const; /** * @return the currently active window or None if there is no window */ Window *activeWindow() const; /** * @return a list of all windows */ QList windows() const; /** * @brief resources returns a list of Resource objects of the given type * @param type Valid types are: * *
    *
  • pattern
  • *
  • gradient
  • *
  • brush
  • *
  • preset
  • *
  • palette
  • *
  • workspace
  • *
*/ QMap resources(const QString &type) const; /** * @brief createDocument creates a new document and image and registers the document with the Krita application. * * The document will have one transparent layer. * * @param width the width in pixels * @param height the height in pixels * @param name the name of the image (not the filename of the document) * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param profile The name of an icc profile that is known to Krita. If an empty string is passed, the default is * taken. * @return the created document. */ Document *createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile); /** * @brief openDocument creates a new Document, registers it with the Krita application and loads the given file. * @param filename the file to open in the document * @return the document */ Document *openDocument(const QString &filename); /** * @brief openWindow create a new main window. The window is not shown by default. */ Window *openWindow(); /** * @brief createAction creates an action with the given text and passes it to Krita. Every newly created * mainwindow will create an instance of this action. * @param text the user-visible text * @return the Action you can connect a slot to. */ Action *createAction(const QString &text); /** * @brief addExtension add the given plugin to Krita. There will be a single instance of each Extension in the Krita process. * @param extension the extension to add. */ void addExtension(Extension* extension); /** * return a list with all registered extension objects. */ QList extensions(); /** * @brief addDockWidgetFactory Add the given docker factory to the application. For scripts * loaded on startup, this means that every window will have one of the dockers created by the * factory. * @param factory The factory object. */ void addDockWidgetFactory(DockWidgetFactoryBase* factory ); /** * @brief writeSetting write the given setting under the given name to the kritarc file in * the given settings group. * @param group The group the setting belongs to. If empty, then the setting is written in the * general section * @param name The name of the setting * @param value The value of the setting. Script settings are always written as strings. */ void writeSetting(const QString &group, const QString &name, const QString &value); /** * @brief readSetting read the given setting value from the kritarc file. * @param group The group the setting is part of. If empty, then the setting is read from * the general group. * @param name The name of the setting * @param defaultValue The default value of the setting * @return a string representing the setting. */ QString readSetting(const QString &group, const QString &name, const QString &defaultValue); /** * @brief instance retrieve the singleton instance of the Application object. */ static Krita* instance(); // Internal only: for use with mikro.py static QObject *fromVariant(const QVariant& v); private: struct Private; Private *const d; static Krita* s_instance; }; Q_DECLARE_METATYPE(Notifier*); #endif // LIBKIS_KRITA_H diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index e7a716d84d..4aff84e937 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,503 +1,568 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #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 "Krita.h" #include "Node.h" #include "Channel.h" #include "Filter.h" #include "Selection.h" struct Node::Private { Private() {} KisImageSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node::~Node() { delete d; } bool Node::operator==(const Node &other) const { return (d->node == other.d->node && d->image == other.d->image); } bool Node::operator!=(const Node &other) const { return !(operator==(other)); } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodes << new Node(d->image, d->node->at(i)); } } return nodes; } bool Node::addChildNode(Node *child, Node *above) { if (!d->node) return false; return d->image->addNode(child->node(), d->node, above->node()); } bool Node::removeChildNode(Node *child) { if (!d->node) return false; return d->image->removeNode(child->node()); } void Node::setChildNodes(QList nodes) { if (!d->node) return; KisNodeSP node = d->node->firstChild(); while (node) { d->image->removeNode(node); node = node->nextSibling(); } Q_FOREACH(Node *node, nodes) { d->image->addNode(node->node(), d->node); } } int Node::colorLabel() const { if (!d->node) return 0; return d->node->colorLabelIndex(); } void Node::setColorLabel(int index) { if (!d->node) return; d->node->setColorLabelIndex(index); } QString Node::colorDepth() const { if (!d->node) return ""; return d->node->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; return d->node->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; return d->node->colorSpace()->profile()->name(); } bool Node::setColorProfile(const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(), srcCS->colorDepthId().id(), profile); KisChangeProfileVisitor v(srcCS, dstCs); return layer->accept(v); } bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); KisColorSpaceConvertVisitor v(d->image, srcCS, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return layer->accept(v); } bool Node::animated() const { if (!d->node) return false; return d->node->isAnimated(); } void Node::enableAnimation() const { if (!d->node) return; d->node->enableAnimation(); } bool Node::collapsed() const { if (!d->node) return false; return d->node->collapsed(); } void Node::setCollapsed(bool collapsed) { if (!d->node) return; d->node->setCollapsed(collapsed); } bool Node::inheritAlpha() const { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; return qobject_cast(d->node)->alphaChannelDisabled(); } void Node::setInheritAlpha(bool value) { if (!d->node) return; if (!d->node->inherits("KisLayer")) return; const_cast(qobject_cast(d->node))->disableAlphaChannel(value); } bool Node::locked() const { if (!d->node) return false; return d->node->userLocked(); } void Node::setLocked(bool value) { if (!d->node) return; d->node->setUserLocked(value); } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString name) { if (!d->node) return; d->node->setName(name); } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; if (value < 0) value = 0; if (value > 255) value = 255; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return new Node(d->image, d->node->parent()); } QString Node::type() const { if (!d->node) return QString(); return QString(); if (qobject_cast(d->node)) { return "paintlayer"; } else if (qobject_cast(d->node)) { return "grouplayer"; } if (qobject_cast(d->node)) { return "filelayer"; } if (qobject_cast(d->node)) { return "filterlayer"; } if (qobject_cast(d->node)) { return "filllayer"; } if (qobject_cast(d->node)) { return "clonelayer"; } if (qobject_cast(d->node)) { return "shapelayer"; } if (qobject_cast(d->node)) { return "transparencymask"; } if (qobject_cast(d->node)) { return "filtermask"; } if (qobject_cast(d->node)) { return "transformmask"; } if (qobject_cast(d->node)) { return "selectionmask"; } if (qobject_cast(d->node)) { return "colorizemask"; } } bool Node::visible() const { if (!d->node) return false; return d->node->visible();; } void Node::setVisible(bool visible) { if (!d->node) return; d->node->setVisible(visible); } QByteArray Node::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } +QByteArray Node::pixelDataAtTime(int x, int y, int w, int h, int time) const +{ + QByteArray ba; + + if (!d->node || !d->node->isAnimated()) return ba; + + // + KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); + if (!rkc) return ba; + KisKeyframeSP frame = rkc->keyframeAt(time); + if (!frame) return ba; + KisPaintDeviceSP dev = d->node->paintDevice(); + if (!dev) return ba; + + rkc->fetchFrame(frame, dev); + + ba.resize(w * h * dev->pixelSize()); + dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); + return ba; +} + QByteArray Node::projectionPixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; d->node->setX(x); d->node->setY(y); } QPoint Node::position() const { if (!d->node) return QPoint(); return QPoint(d->node->x(), d->node->y()); } bool Node::remove() { if (!d->node) return false; if (!d->node->parent()) return false; return d->image->removeNode(d->node); } Node* Node::duplicate() { if (!d->node) return 0; return new Node(d->image, d->node->clone()); } bool Node::save(const QString &filename, double xRes, double yRes) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = d->node->exactBounds(); QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);; QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->setOutputMimeType(mimefilter.toLatin1()); bool r = doc->exportDocument(QUrl::fromLocalFile(filename)); if (!r) { qWarning() << doc->errorMessage(); } return r; } Node *Node::mergeDown() { if (!d->node) return 0; if (!qobject_cast(d->node.data())) return 0; if (!d->node->nextSibling()) return 0; if (!d->node->parent()) return 0; int index = d->node->parent()->index(d->node->prevSibling()); d->image->mergeDown(qobject_cast(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); d->image->waitForDone(); return new Node(d->image, d->node->parent()->at(index)); } +void Node::scaleNode(int width, int height, QString strategy) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); + if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + + d->image->scaleNode(d->node, width, height, actualStrategy); +} + +void Node::rotateNode(double radians) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + d->image->rotateNode(d->node, radians); +} + +void Node::cropNode(int x, int y, int w, int h) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + QRect rect = QRect(x, y, w, h); + d->image->cropNode(d->node, rect); +} + +void Node::shearNode(double angleX, double angleY) +{ + if (!d->node) return; + if (!qobject_cast(d->node.data())) return; + if (!d->node->parent()) return; + + d->image->shearNode(d->node, angleX, angleY); +} + QImage Node::thumbnail(int w, int h) { if (!d->node) return QImage(); return d->node->createThumbnail(w, h); } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h index f05103cb3c..17386c8474 100644 --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -1,471 +1,522 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_NODE_H #define LIBKIS_NODE_H #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * Node represents a layer or mask in a Krita image's Node hierarchy. Group layers can contain * other layers and masks; layers can contain masks. * */ class KRITALIBKIS_EXPORT Node : public QObject { Q_OBJECT Q_DISABLE_COPY(Node) public: explicit Node(KisImageSP image, KisNodeSP node, QObject *parent = 0); ~Node() override; bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: /** * @brief alphaLocked checks whether the node is a paint layer and returns whether it is alpha locked * @return whether the paint layer is alpha locked, or false if the node is not a paint layer */ bool alphaLocked() const; /** * @brief setAlphaLocked set the layer to value if the the node is paint layer. */ void setAlphaLocked(bool value); /** * @return the blending mode of the layer. The values of the blending modes are defined in @see KoCompositeOpRegistry.h */ QString blendingMode() const; /** * @brief setBlendingMode set the blending mode of the node to the given value * @param value one of the string values from @see KoCompositeOpRegistry.h */ void setBlendingMode(QString value); /** * @brief channels creates a list of Channel objects that can be used individually to * show or hide certain channels, and to retrieve the contents of each channel in a * node separately. * * Only layers have channels, masks do not, and calling channels on a Node that is a mask * will return an empty list. * * @return the list of channels ordered in by position of the channels in pixel position */ QList channels() const; /** * Return a list of child nodes of the current node. The nodes are ordered from the bottommost up. * The function is not recursive. */ QList childNodes() const; /** * @brief addChildNode adds the given node in the list of children. * @param child the node to be added * @param above the node above which this node will be placed * @return false if adding the node failed */ bool addChildNode(Node *child, Node *above); /** * @brief removeChildNode removes the given node from the list of children. * @param child the node to be removed */ bool removeChildNode(Node *child); /** * @brief setChildNodes this replaces the existing set of child nodes with the new set. * @param nodes The list of nodes that will become children, bottom-up -- the first node, * is the bottom-most node in the stack. */ void setChildNodes(QList nodes); /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return if assigining the colorprofiel worked */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the node to the given colorspace * @param colorModel A string describing the color model of the node: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief Krita layers can be animated, i.e., have frames. * @return return true if the layer has frames. Currently, the scripting framework * does not give access to the animation features. */ bool animated() const; /** * @brief enableAnimation make the current layer animated, so it can have frames. */ void enableAnimation() const; /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ int colorLabel() const; /** * @brief setColorLabel sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. * @param index an integer corresponding to the set of available color labels. */ void setColorLabel(int index); /** * @brief inheritAlpha checks whether this node has the inherits alpha flag set * @return true if the Inherit Alpha is set */ bool inheritAlpha() const; /** * set the Inherit Alpha flag to the given value */ void setInheritAlpha(bool value); /** * @brief locked checkes whether the Node is locked. A locked node cannot be changed. * @return true if the Node is locked, false if it hasn't been locked. */ bool locked() const; /** * set the Locked flag to the give value */ void setLocked(bool value); /** * @return the user-visible name of this node. */ QString name() const; /** * rename the Node to the given name */ void setName(QString name); /** * return the opacity of the Node. The opacity is a value between 0 and 255. */ int opacity() const; /** * set the opacity of the Node to the given value. The opacity is a value between 0 and 255. */ void setOpacity(int value); /** * return the Node that is the parent of the current Node, or 0 if this is the root Node. */ Node* parentNode() const; /** * @brief type Krita has several types of nodes, split in layers and masks. Group * layers can contain other layers, any layer can contain masks. * * @return The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
  • colorizemask *
* * If the Node object isn't wrapping a valid Krita layer or mask object, and * empty string is returned. */ QString type() const; /** * Check whether the current Node is visible in the layer stack */ bool visible() const; /** * Set the visibility of the current node to @param visible */ void setVisible(bool visible); /** * @brief pixelData reads the given rectangle from the Node's paintable pixels, if those * exist, and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the pixeldata of a mask, a filter or generator layer, you get the selection bytes, * which is one channel with values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; + /** + * @brief pixelDataAtTime a basic function to get pixeldata from an animated node at a given time. + * @param x the position from the left to start reading. + * @param y the position from the top to start reader + * @param w the row length to read + * @param h the number of rows to read + * @param time the frame number + * @return a QByteArray with the pixel data. The byte array may be empty. + */ + QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; + /** * @brief projectionPixelData reads the given rectangle from the Node's projection (that is, what the node * looks like after all sub-Nodes (like layers in a group or masks on a layer) have been applied, * and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the projection of a mask, you get the selection bytes, which is one channel with * values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray projectionPixelData(int x, int y, int w, int h) const; /** * @brief setPixelData writes the given bytes, of which there must be enough, into the * Node, if the Node has writable pixel data: * *
    *
  • paint layer: the layer's original pixels are overwritten *
  • filter layer, generator layer, any mask: the embedded selection's pixels are overwritten. * Note: for these *
* * File layers, Group layers, Clone layers cannot be written to. Calling setPixelData on * those layer types will silently do nothing. * * @param value the byte array representing the pixels. There must be enough bytes available. * Krita will take the raw pointer from the QByteArray and start reading, not stopping before * (number of channels * size of channel * w * h) bytes are read. * * @param x the x position to start writing from * @param y the y position to start writing from * @param w the width of each row * @param h the number of rows to write */ void setPixelData(QByteArray value, int x, int y, int w, int h); /** * @brief bounds return the exact bounds of the node's paint device * @return the bounds, or an empty QRect if the node has no paint device or is empty. */ QRect bounds() const; /** * move the pixels to the given x, y location in the image coordinate space. */ void move(int x, int y); /** * @brief position returns the position of the paint device of this node * @return the top-left position of the node */ QPoint position() const; /** * @brief remove removes this node from its parent image. */ bool remove(); /** * @brief duplicate returns a full copy of the current node. The node is not inserted in the graphc * @return a valid Node object or 0 if the node couldn't be duplicated. */ Node* duplicate(); /** * @brief save exports the given node with this filename. The extension of the filename determins the filetype. * @param filename the filename including extension * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @return true if saving succeeded, false if it failed. */ bool save(const QString &filename, double xRes, double yRes); /** * @brief mergeDown merges the given node with the first visible node underneath this node in the layerstack. * This will drop all per-layer metadata. * @param node the node to merge down; this node will be removed from the layer stack */ Node *mergeDown(); + /** + * @brief scaleNode + * @param width + * @param height + * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. + *
    + *
  • Hermite
  • + *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • + *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • + *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • + *
  • Bell
  • + *
  • BSpline
  • + *
  • Lanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • + *
  • Mitchell
  • + *
+ */ + void scaleNode(int width, int height, QString strategy); + + /** + * @brief rotateNode rotate this layer by the given radians. + * @param radians amount the layer should be rotated in, in radians. + */ + void rotateNode(double radians); + + /** + * @brief cropNode crop this layer. + * @param x the left edge of the cropping rectangle. + * @param y the top edge of the cropping rectangle + * @param w the right edge of the cropping rectangle + * @param h the bottom edge of the cropping rectangle + */ + void cropNode(int x, int y, int w, int h); + + /** + * @brief shearNode perform a shear operation on this node. + * @param angleX the X-angle in degrees to shear by + * @param angleY the Y-angle in degrees to shear by + */ + void shearNode(double angleX, double angleY); + /** * @brief thumbnail create a thumbnail of the given dimensions. The thumbnail is sized according * to the layer dimensions, not the image dimensions. If the requested size is too big a null * QImage is created. If the current node cannot generate a thumbnail, a transparent QImage of the * requested size is generated. * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h); private: friend class Filter; friend class Document; friend class Selection; /** * @brief paintDevice gives access to the internal paint device of this Node * @return the paintdevice or 0 if the node does not have an editable paint device. */ KisPaintDeviceSP paintDevice() const; KisImageSP image() const; KisNodeSP node() const; struct Private; Private *const d; }; #endif // LIBKIS_NODE_H diff --git a/libs/odf/KoColumns.h b/libs/odf/KoColumns.h index dc6b4e829e..cf068a0cf9 100644 --- a/libs/odf/KoColumns.h +++ b/libs/odf/KoColumns.h @@ -1,137 +1,138 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright 2002, 2003 David Faure Copyright 2003 Nicolas GOUTTE Copyright 2007 Thomas Zander 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. */ #ifndef KOCOLUMNS_H #define KOCOLUMNS_H #include "kritaodf_export.h" #include #include +#include + class KoGenStyle; -class KoXmlElement; /** structure for columns */ struct KoColumns { enum SeparatorVerticalAlignment { AlignTop = Qt::AlignTop, AlignVCenter = Qt::AlignVCenter, AlignBottom = Qt::AlignBottom }; enum SeparatorStyle { None = Qt::NoPen, Solid = Qt::SolidLine, Dashed = Qt::DashLine, Dotted = Qt::DotLine, DotDashed = Qt::DashDotLine }; struct ColumnDatum { /** Left indent in points */ qreal leftMargin; /** Right indent in points */ qreal rightMargin; /** Top indent in points */ qreal topMargin; /** Bottom indent in points */ qreal bottomMargin; /** The relative width */ int relativeWidth; ColumnDatum() {} ColumnDatum(qreal lm, qreal rm, qreal tm, qreal bm, int rw) : leftMargin(lm), rightMargin(rm), topMargin(tm), bottomMargin(bm), relativeWidth(rw) {} bool operator==(const KoColumns::ColumnDatum &rhs) const { return (leftMargin == rhs.leftMargin) && (rightMargin == rhs.rightMargin) && (topMargin == rhs.topMargin) && (bottomMargin == rhs.bottomMargin) && (relativeWidth == rhs.relativeWidth); } }; /** Number of columns */ int count; /** Spacing between columns in points */ qreal gapWidth; SeparatorStyle separatorStyle; QColor separatorColor; SeparatorVerticalAlignment separatorVerticalAlignment; /** Width in pt */ qreal separatorWidth; /** Height in percent. Default is 100% */ unsigned int separatorHeight; /** data about the individual columns if there */ QList columnData; /** * Construct a columns with the default column count 1, * default margins (2 cm), and portrait orientation. */ KRITAODF_EXPORT KoColumns(); KRITAODF_EXPORT void reset(); KRITAODF_EXPORT bool operator==(const KoColumns &l) const; KRITAODF_EXPORT bool operator!=(const KoColumns &l) const; /** * Save this columns to ODF. */ KRITAODF_EXPORT void saveOdf(KoGenStyle &style) const; /** * Load this columns from ODF */ KRITAODF_EXPORT void loadOdf(const KoXmlElement &style); qreal totalRelativeWidth() const { qreal result = 0.0; Q_FOREACH (const ColumnDatum &c, columnData) { result += c.relativeWidth; } return result; } KRITAODF_EXPORT static const char * separatorStyleString(KoColumns::SeparatorStyle separatorStyle); KRITAODF_EXPORT static const char * separatorVerticalAlignmentString(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment); KRITAODF_EXPORT static KoColumns::SeparatorVerticalAlignment parseSeparatorVerticalAlignment(const QString &value); KRITAODF_EXPORT static QColor parseSeparatorColor(const QString &value); KRITAODF_EXPORT static int parseSeparatorHeight(const QString &value); KRITAODF_EXPORT static KoColumns::SeparatorStyle parseSeparatorStyle(const QString &value); KRITAODF_EXPORT static int parseRelativeWidth(const QString &value); }; #endif /* KOCOLUMNS_H */ diff --git a/libs/odf/KoOdfReadStore.cpp b/libs/odf/KoOdfReadStore.cpp index c02e88d0df..505dd7d138 100644 --- a/libs/odf/KoOdfReadStore.cpp +++ b/libs/odf/KoOdfReadStore.cpp @@ -1,145 +1,141 @@ /* This file is part of the KDE project Copyright (C) 2005 David Faure Copyright (C) 2007 Thorsten Zach3n 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 "KoOdfReadStore.h" #include #include #include #include #include "KoOdfStylesReader.h" #include class Q_DECL_HIDDEN KoOdfReadStore::Private { public: Private(KoStore *s) : store(s) { } KoStore * store; KoOdfStylesReader stylesReader; // it is needed to keep the stylesDoc around so that you can access the styles KoXmlDocument stylesDoc; KoXmlDocument contentDoc; KoXmlDocument settingsDoc; }; KoOdfReadStore::KoOdfReadStore(KoStore *store) : d(new Private(store)) { } KoOdfReadStore::~KoOdfReadStore() { delete d; } KoStore * KoOdfReadStore::store() const { return d->store; } KoOdfStylesReader &KoOdfReadStore::styles() { return d->stylesReader; } KoXmlDocument KoOdfReadStore::contentDoc() const { return d->contentDoc; } KoXmlDocument KoOdfReadStore::settingsDoc() const { return d->settingsDoc; } bool KoOdfReadStore::loadAndParse(QString &errorMessage) { if (!loadAndParse("content.xml", d->contentDoc, errorMessage)) { return false; } if (d->store->hasFile("styles.xml")) { if (!loadAndParse("styles.xml", d->stylesDoc, errorMessage)) { return false; } } // Load styles from style.xml d->stylesReader.createStyleMap(d->stylesDoc, true); // Also load styles from content.xml d->stylesReader.createStyleMap(d->contentDoc, false); if (d->store->hasFile("settings.xml")) { loadAndParse("settings.xml", d->settingsDoc, errorMessage); } return true; } bool KoOdfReadStore::loadAndParse(const QString &fileName, KoXmlDocument &doc, QString &errorMessage) { if (!d->store) { errorMessage = i18n("No store backend"); return false; } if (!d->store->isOpen()) { if (!d->store->open(fileName)) { debugOdf << "Entry " << fileName << " not found!"; // not a warning as embedded stores don't have to have all files errorMessage = i18n("Could not find %1", fileName); return false; } } bool ok = loadAndParse(d->store->device(), doc, errorMessage, fileName); d->store->close(); return ok; } bool KoOdfReadStore::loadAndParse(QIODevice *fileDevice, KoXmlDocument &doc, QString &errorMessage, const QString &fileName) { // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; if (!fileDevice->isOpen()) { fileDevice->open(QIODevice::ReadOnly); } - - QXmlStreamReader reader(fileDevice); - reader.setNamespaceProcessing(true); - - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(fileDevice, true, &errorMsg, &errorLine, &errorColumn); if (!ok) { errorOdf << "Parsing error in " << fileName << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errorMessage = i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } else { debugOdf << "File" << fileName << " loaded and parsed"; } return ok; } diff --git a/libs/odf/tests/CMakeLists.txt b/libs/odf/tests/CMakeLists.txt index 2c81799249..9dbf046647 100644 --- a/libs/odf/tests/CMakeLists.txt +++ b/libs/odf/tests/CMakeLists.txt @@ -1,23 +1,22 @@ 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 - TestXmlReaderWithoutSpaces.cpp kodomtest.cpp TestKoUnit.cpp TestNumberStyle.cpp TestKoElementReference.cpp NAME_PREFIX "libs-odf-" LINK_LIBRARIES kritaodf Qt5::Test) diff --git a/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp b/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp deleted file mode 100644 index 1d3449ed3e..0000000000 --- a/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp +++ /dev/null @@ -1,2696 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include - - -class TestXmlReaderWithoutSpaces : 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 TestXmlReaderWithoutSpaces::testNode() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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), 2); - 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(); - 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), 6); - 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), 6); - 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 TestXmlReaderWithoutSpaces::testElement() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << "

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

"; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testAttributes() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << "

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

"; - xmldevice.close(); - - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testText() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << "

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

"; - xmldevice.close(); - - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testCDATA() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << "

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

"; - xmldevice.close(); - - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testDocument() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << "\n"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - - // 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 TestXmlReaderWithoutSpaces::testDocumentType() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "

"; - xmlstream << "

"; - xmlstream << ""; - xmldevice.close(); - - // empty document - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testNamespace() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // 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(false); - 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.firstChild().toElement(); - QCOMPARE(bookElement.isNull(), false); - QCOMPARE(bookElement.isElement(), true); - QCOMPARE(bookElement.tagName(), QString("book")); - QCOMPARE(bookElement.prefix().isNull(), true); - QCOMPARE(bookElement.localName(), QString()); - - bookTitleElement = bookElement.firstChild().toElement(); - QCOMPARE(bookTitleElement.isNull(), false); - QCOMPARE(bookTitleElement.isElement(), true); - QCOMPARE(bookTitleElement.tagName(), QString("book:title")); - QCOMPARE(bookTitleElement.prefix().isNull(), true); - QCOMPARE(bookTitleElement.localName(), QString()); - - bookAuthorElement = bookTitleElement.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.firstChild().toElement(); - 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")); - - // - bookAuthorElement = bookTitleElement.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 TestXmlReaderWithoutSpaces::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(false); - 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.firstChild().toElement(); - 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")); - - // - bookAuthorElement = bookTitleElement.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 TestXmlReaderWithoutSpaces::testUnload() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testSimpleXML() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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), 5); - QCOMPARE(rootElement.tagName(), QString("solarsystem")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // node - KoXmlNode firstPlanetNode; - firstPlanetNode = rootElement.firstChild(); - QCOMPARE(firstPlanetNode.isNull(), false); - QCOMPARE(firstPlanetNode.isElement(), true); - QCOMPARE(firstPlanetNode.nextSibling().isNull(), false); - QCOMPARE(firstPlanetNode.previousSibling().isNull(), true); - 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); - - // node - KoXmlNode secondPlanetNode; - secondPlanetNode = firstPlanetNode.nextSibling(); - QCOMPARE(secondPlanetNode.isNull(), false); - QCOMPARE(secondPlanetNode.isElement(), true); - QCOMPARE(secondPlanetNode.nextSibling().isNull(), false); - QCOMPARE(secondPlanetNode.previousSibling().isNull(), false); - QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetNode, true); - QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetElement, 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() == firstPlanetNode, true); - QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetElement, 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 TestXmlReaderWithoutSpaces::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 << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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 TestXmlReaderWithoutSpaces::testMismatchedTag() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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); -} - -static void dumpNodes(const KoXmlNode &node, int level=0) -{ - QString indent = QString("%1").arg("", level*3); - if (node.isNull()) { - qDebug()<"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "

The best place

"; - xmlstream << ""; - xmlstream << "
"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "
"; - xmldevice.close(); - - KoXmlDocument doc(false); - QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - dumpNodes(doc); - // - 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), 5); - 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(); - qDebug()<<"placeNode"<"; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << "

The best place

"; - xmlstream << ""; - xmlstream << "
"; - xmlstream << ""; - xmlstream << ""; - xmlstream << "
"; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc(false); - 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), 1); - QCOMPARE(rootElement.tagName(), QString("universe")); - QCOMPARE(rootElement.prefix().isNull(), true); - - // now test converting KoXmlElement to QDomElement - QDomDocument solarDoc; - KoXml::asQDomElement(solarDoc, rootElement.firstChild().toElement()); - - // - 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 TestXmlReaderWithoutSpaces::testSimpleOpenDocumentText() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // 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(false); - 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), 2); - 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(), true); - QCOMPARE(stylesElement.nextSibling().isNull(), false); - QCOMPARE(stylesElement.localName(), QString("automatic-styles")); - - // also same , but without namedItemNS - KoXmlNode styles2Element; - styles2Element = contentElement.firstChild().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(), 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), 1); - 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")); - - // also same , but without namedItemNS - KoXmlElement body2Element; - body2Element = stylesElement.nextSibling().toElement(); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.parentNode().isNull(), false); - QCOMPARE(body2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(body2Element), 1); - 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), 1); - QCOMPARE(textElement.firstChild().isNull(), false); - QCOMPARE(textElement.lastChild().isNull(), false); - QCOMPARE(textElement.previousSibling().isNull(), true); - QCOMPARE(textElement.nextSibling().isNull(), true); - QCOMPARE(textElement.localName(), QString("text")); - - // the same , but without namedItemNS - KoXmlElement text2Element; - text2Element = bodyElement.firstChild().toElement(); - QCOMPARE(text2Element.isNull(), false); - QCOMPARE(text2Element.isElement(), true); - QCOMPARE(text2Element.parentNode().isNull(), false); - QCOMPARE(text2Element.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(text2Element), 1); - QCOMPARE(text2Element.firstChild().isNull(), false); - QCOMPARE(text2Element.lastChild().isNull(), false); - QCOMPARE(text2Element.previousSibling().isNull(), true); - QCOMPARE(text2Element.nextSibling().isNull(), true); - QCOMPARE(text2Element.localName(), QString("text")); - - // - KoXmlElement parElement; - parElement = textElement.firstChild().toElement(); - 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(), true); - QCOMPARE(parElement.nextSibling().isNull(), true); - QCOMPARE(parElement.tagName(), QString("p")); - QCOMPARE(parElement.text(), QString("Hello, world!")); - QCOMPARE(parElement.attributeNS(QString(textNS), "style-name", ""), QString("Standard")); -} - -void TestXmlReaderWithoutSpaces::testWhitespace() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // 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(false); - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement p1; - p1 = doc.documentElement().firstChild().toElement(); - QCOMPARE(p1.isNull(), false); - QCOMPARE(p1.isElement(), true); - QCOMPARE(KoXml::childNodesCount(p1), 1); - - KoXmlElement p2; - p2 = p1.nextSibling().toElement(); - QCOMPARE(p2.isNull(), false); - QCOMPARE(p2.isElement(), true); - QCOMPARE(KoXml::childNodesCount(p2), 3); -} - -void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentSpreadsheet() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // 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(false); - 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 TestXmlReaderWithoutSpaces::testSimpleOpenDocumentPresentation() -{ - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - - // 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(false); - 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), 3); - 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(), 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")); - - // also same , but without namedItemNS - KoXmlNode styles2Element; - styles2Element = scriptsElement.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), 1); - 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")); - - // also same , but without namedItemNS - KoXmlElement body2Element; - body2Element = stylesElement.nextSibling().toElement(); - QCOMPARE(body2Element.isNull(), false); - QCOMPARE(body2Element.isElement(), true); - QCOMPARE(body2Element.parentNode().isNull(), false); - QCOMPARE(body2Element.parentNode() == contentElement, true); - QCOMPARE(KoXml::childNodesCount(body2Element), 1); - 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), 2); - QCOMPARE(presentationElement.firstChild().isNull(), false); - QCOMPARE(presentationElement.lastChild().isNull(), false); - QCOMPARE(presentationElement.previousSibling().isNull(), true); - QCOMPARE(presentationElement.nextSibling().isNull(), true); - QCOMPARE(presentationElement.localName(), QString("presentation")); - - // the same , but without namedItemNS - KoXmlElement presentation2Element; - presentation2Element = bodyElement.firstChild().toElement(); - QCOMPARE(presentation2Element.isNull(), false); - QCOMPARE(presentation2Element.isElement(), true); - QCOMPARE(presentation2Element.parentNode().isNull(), false); - QCOMPARE(presentation2Element.parentNode() == bodyElement, true); - QCOMPARE(KoXml::childNodesCount(presentation2Element), 2); - QCOMPARE(presentation2Element.firstChild().isNull(), false); - QCOMPARE(presentation2Element.lastChild().isNull(), false); - QCOMPARE(presentation2Element.previousSibling().isNull(), true); - QCOMPARE(presentation2Element.nextSibling().isNull(), true); - QCOMPARE(presentation2Element.localName(), QString("presentation")); - - // for "Title" - KoXmlElement titlePageElement; - titlePageElement = presentationElement.firstChild().toElement(); - QCOMPARE(titlePageElement.isNull(), false); - QCOMPARE(titlePageElement.isElement(), true); - QCOMPARE(titlePageElement.parentNode().isNull(), false); - QCOMPARE(titlePageElement.parentNode() == presentationElement, true); - QCOMPARE(KoXml::childNodesCount(titlePageElement), 3); - QCOMPARE(titlePageElement.firstChild().isNull(), false); - QCOMPARE(titlePageElement.lastChild().isNull(), false); - QCOMPARE(titlePageElement.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.firstChild().toElement(); - QCOMPARE(titleFrameElement.isNull(), false); - QCOMPARE(titleFrameElement.isElement(), true); - QCOMPARE(titleFrameElement.parentNode().isNull(), false); - QCOMPARE(titleFrameElement.parentNode() == titlePageElement, true); - QCOMPARE(KoXml::childNodesCount(titleFrameElement), 1); - QCOMPARE(titleFrameElement.firstChild().isNull(), false); - QCOMPARE(titleFrameElement.lastChild().isNull(), false); - QCOMPARE(titleFrameElement.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.firstChild().toElement(); - QCOMPARE(titleBoxElement.isNull(), false); - QCOMPARE(titleBoxElement.isElement(), true); - QCOMPARE(titleBoxElement.parentNode().isNull(), false); - QCOMPARE(titleBoxElement.parentNode() == titleFrameElement, true); - QCOMPARE(KoXml::childNodesCount(titleBoxElement), 1); - QCOMPARE(titleBoxElement.firstChild().isNull(), false); - QCOMPARE(titleBoxElement.lastChild().isNull(), false); - QCOMPARE(titleBoxElement.previousSibling().isNull(), true); - QCOMPARE(titleBoxElement.nextSibling().isNull(), true); - QCOMPARE(titleBoxElement.localName(), QString("text-box")); - - // for the title text-box - KoXmlElement titleParElement; - titleParElement = titleBoxElement.firstChild().toElement(); - 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(), true); - QCOMPARE(titleParElement.nextSibling().isNull(), true); - QCOMPARE(titleParElement.localName(), QString("p")); - QCOMPARE(titleParElement.attributeNS(textNS, "style-name", ""), QString("P1")); - QCOMPARE(titleParElement.text(), QString("Foobar")); - - // for the subtitle frame - KoXmlElement subtitleFrameElement; - subtitleFrameElement = titleFrameElement.nextSibling().toElement(); - QCOMPARE(subtitleFrameElement.isNull(), false); - QCOMPARE(subtitleFrameElement.isElement(), true); - QCOMPARE(subtitleFrameElement.parentNode().isNull(), false); - QCOMPARE(subtitleFrameElement.parentNode() == titlePageElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleFrameElement), 1); - 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.firstChild().toElement(); - QCOMPARE(subtitleBoxElement.isNull(), false); - QCOMPARE(subtitleBoxElement.isElement(), true); - QCOMPARE(subtitleBoxElement.parentNode().isNull(), false); - QCOMPARE(subtitleBoxElement.parentNode() == subtitleFrameElement, true); - QCOMPARE(KoXml::childNodesCount(subtitleBoxElement), 1); - QCOMPARE(subtitleBoxElement.firstChild().isNull(), false); - QCOMPARE(subtitleBoxElement.lastChild().isNull(), false); - QCOMPARE(subtitleBoxElement.previousSibling().isNull(), true); - QCOMPARE(subtitleBoxElement.nextSibling().isNull(), true); - QCOMPARE(subtitleBoxElement.localName(), QString("text-box")); - - // for the subtitle text-box - KoXmlElement subtitleParElement; - subtitleParElement = subtitleBoxElement.firstChild().toElement(); - 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(), true); - QCOMPARE(subtitleParElement.nextSibling().isNull(), true); - QCOMPARE(subtitleParElement.localName(), QString("p")); - QCOMPARE(subtitleParElement.attributeNS(textNS, "style-name", ""), QString("P3")); - QCOMPARE(subtitleParElement.text(), QString("Foo")); -} - -void TestXmlReaderWithoutSpaces::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(false); - 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(), true); - QCOMPARE(semanticsElement.nextSibling().isNull(), true); - QCOMPARE(semanticsElement.localName(), QString("semantics")); - - // the same but without namedItemNS - KoXmlElement semantics2Element; - semantics2Element = mathElement.firstChild().toElement(); - 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(), true); - QCOMPARE(semantics2Element.nextSibling().isNull(), true); - QCOMPARE(semantics2Element.localName(), QString("semantics")); - - // - KoXmlElement mrowElement; - mrowElement = semanticsElement.firstChild().toElement(); - 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(), true); - QCOMPARE(mrowElement.nextSibling().isNull(), false); - QCOMPARE(mrowElement.localName(), QString("mrow")); - - // for "E" - KoXmlElement miElement; - miElement = mrowElement.firstChild().toElement(); - 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(), true); - QCOMPARE(miElement.nextSibling().isNull(), false); - QCOMPARE(miElement.localName(), QString("mi")); - - // for "=" - KoXmlElement moElement; - moElement = miElement.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")); - - // for "mc" and superscripted "2" - KoXmlElement msupElement; - msupElement = moElement.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.nextSibling().isNull(), true); - QCOMPARE(msupElement.localName(), QString("msup")); - - // inside the for "mc" - KoXmlElement mcElement; - mcElement = msupElement.firstChild().toElement(); - 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(), 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) - KoXmlElement mnElement; - mnElement = mcElement.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(), true); - QCOMPARE(mnElement.localName(), QString("mn")); - QCOMPARE(mnElement.text(), QString("2")); - - // - KoXmlElement annotationElement; - annotationElement = semanticsElement.lastChild().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(), true); - QCOMPARE(annotationElement.localName(), QString("annotation")); - QCOMPARE(annotationElement.text(), QString("E = mc^2 ")); - QCOMPARE(annotationElement.attributeNS(mathNS, "encoding", ""), QString("StarMath 5.0")); -} - -void TestXmlReaderWithoutSpaces::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 << ""; - xmlstream << ""; - xmlstream << ""; - for (int i = 0; i < sheetCount; i++) { - QString sheetName = QString("Sheet%1").arg(i + 1); - xmlstream << ""; - for (int j = 0; j < rowCount; j++) { - xmlstream << ""; - for (int k = 0; k < colCount; k++) { - xmlstream << ""; - xmlstream << "Hello, world"; - xmlstream << ""; - } - xmlstream << ""; - } - xmlstream << ""; - } - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - printf("Raw XML size: %lld KB\n", xmldevice.size() / 1024); - - - QTime timer; - -#if 0 - // just to test parsing speed with plain dumb handler - QXmlStreamReader *reader = new QXmlStreamReader(xmldevice); - reader->setNamespaceProcessing(true); - timer.start(); - ParseError error = parseDocument(*reader, doc); - printf("Large spreadsheet: QXmlStreamReader parsing time is %d ms\n", timer.elapsed()); - delete reader; - xmldevice.seek(0); -#endif - - KoXmlDocument doc(false); - - 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.firstChild().toElement(); - QCOMPARE(bodyElement.isNull(), false); - QCOMPARE(bodyElement.isElement(), true); - QCOMPARE(bodyElement.localName(), QString("body")); - - // - KoXmlElement spreadsheetElement; - spreadsheetElement = bodyElement.firstChild().toElement(); - 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.firstChild().toElement(); - 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.firstChild().toElement(); - 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.firstChild().toElement(); - 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().toElement(); - } - - //KoXml::unload( rowElement ); - rowElement = rowElement.nextSibling().toElement(); - } - - KoXml::unload(tableElement); - tableElement = tableElement.nextSibling().toElement(); - } - - printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed()); -} - -void TestXmlReaderWithoutSpaces::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("Procesing 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(false); - - 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(TestXmlReaderWithoutSpaces) -#include - diff --git a/libs/pigment/KoColorConversionAlphaTransformation.cpp b/libs/pigment/KoColorConversionAlphaTransformation.cpp index 5289cafe5d..0e9e23e919 100644 --- a/libs/pigment/KoColorConversionAlphaTransformation.cpp +++ b/libs/pigment/KoColorConversionAlphaTransformation.cpp @@ -1,347 +1,351 @@ /* * 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 "KoColorConversionAlphaTransformation.h" #include "KoColorSpace.h" #include "KoIntegerMaths.h" #include "KoColorSpaceTraits.h" #include "KoColorModelStandardIds.h" #include "KoColorModelStandardIdsUtils.h" /** * Converter from the alpha color space to any color space */ template class KoColorConversionFromAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionFromAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const alpha_channel_type *srcPtr = reinterpret_cast(src); quint16 data[4]; const qint32 pixelSize = dstColorSpace()->pixelSize(); data[1] = UINT16_MAX / 2; // a data[2] = UINT16_MAX / 2; // b data[3] = UINT16_MAX; // A while (nPixels > 0) { data[0] = KoColorSpaceMaths::scaleToA(*srcPtr); // L dstColorSpace()->fromLabA16((quint8*)data, dst, 1); srcPtr++; dst += pixelSize; nPixels--; } } }; template class KoColorConversionAlphaToLab16Transformation : public KoColorConversionTransformation { public: KoColorConversionAlphaToLab16Transformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const alpha_channel_type *srcPtr = reinterpret_cast(src); quint16 *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { dstPtr[0] = KoColorSpaceMaths::scaleToA(*srcPtr); // L dstPtr[1] = UINT16_MAX / 2; // a dstPtr[2] = UINT16_MAX / 2; // b dstPtr[3] = UINT16_MAX; // A srcPtr++; dstPtr += 4; nPixels--; } } }; template class KoColorConversionGrayAFromAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionGrayAFromAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const alpha_channel_type *srcPtr = reinterpret_cast(src); gray_channel_type *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { dstPtr[0] = KoColorSpaceMaths::scaleToA(*srcPtr); dstPtr[1] = KoColorSpaceMathsTraits::unitValue; srcPtr++; dstPtr += 2; nPixels--; } } }; //------ KoColorConversionFromAlphaTransformationFactoryImpl ------// template KoColorConversionFromAlphaTransformationFactoryImpl:: KoColorConversionFromAlphaTransformationFactoryImpl(const QString& _dstModelId, const QString& _dstDepthId, const QString& _dstProfileName) : KoColorConversionTransformationFactory(AlphaColorModelID.id(), colorDepthIdForChannelType().id(), "default", _dstModelId, _dstDepthId, _dstProfileName) { } template KoColorConversionTransformation* KoColorConversionFromAlphaTransformationFactoryImpl:: createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(canBeSource(srcColorSpace)); Q_ASSERT(canBeDestination(dstColorSpace)); if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Integer8BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); + #ifdef HAVE_OPENEXR } else if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Float16BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); + #endif } else if (dstColorSpace->colorModelId() == GrayAColorModelID && dstColorSpace->colorDepthId() == Float32BitsColorDepthID) { return new KoColorConversionGrayAFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (dstColorSpace->colorModelId() == LABAColorModelID && dstColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionAlphaToLab16Transformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { return new KoColorConversionFromAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } } template bool KoColorConversionFromAlphaTransformationFactoryImpl:: conserveColorInformation() const { return false; } template bool KoColorConversionFromAlphaTransformationFactoryImpl:: conserveDynamicRange() const { return false; } template class KoColorConversionFromAlphaTransformationFactoryImpl; template class KoColorConversionFromAlphaTransformationFactoryImpl; #ifdef HAVE_OPENEXR template class KoColorConversionFromAlphaTransformationFactoryImpl; #endif template class KoColorConversionFromAlphaTransformationFactoryImpl; //------ KoColorConversionToAlphaTransformation ------// /** * Converter to the alpha color space to any color space */ template class KoColorConversionToAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionToAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { alpha_channel_type *dstPtr = reinterpret_cast(dst); quint16 data[4]; qint32 pixelSize = srcColorSpace()->pixelSize(); while (nPixels > 0) { srcColorSpace()->toLabA16(src, (quint8*)data, 1); *dstPtr = KoColorSpaceMaths::scaleToA(UINT16_MULT(data[0], data[3])); // L * A src += pixelSize; dstPtr ++; nPixels --; } } }; template class KoColorConversionLab16ToAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionLab16ToAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const quint16 *srcPtr = reinterpret_cast(src); alpha_channel_type *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { *dstPtr = KoColorSpaceMaths::scaleToA(UINT16_MULT(srcPtr[0], srcPtr[3])); // L * A srcPtr += 4; dstPtr++; nPixels--; } } }; //------ KoColorConversionGrayAU8ToAlphaTransformation ------// template class KoColorConversionGrayAToAlphaTransformation : public KoColorConversionTransformation { public: KoColorConversionGrayAToAlphaTransformation(const KoColorSpace* srcCs, const KoColorSpace* dstCs, Intent renderingIntent, ConversionFlags conversionFlags) : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const gray_channel_type *srcPtr = reinterpret_cast(src); alpha_channel_type *dstPtr = reinterpret_cast(dst); while (nPixels > 0) { *dstPtr = KoColorSpaceMaths::scaleToA( KoColorSpaceMaths::multiply(srcPtr[0], srcPtr[1])); srcPtr += 2; dstPtr++; nPixels --; } } }; //------ KoColorConversionToAlphaTransformationFactoryImpl ------// template KoColorConversionToAlphaTransformationFactoryImpl:: KoColorConversionToAlphaTransformationFactoryImpl(const QString& _srcModelId, const QString& _srcDepthId, const QString& _srcProfileName) : KoColorConversionTransformationFactory(_srcModelId, _srcDepthId, _srcProfileName, AlphaColorModelID.id(), colorDepthIdForChannelType().id(), "default") { } template KoColorConversionTransformation* KoColorConversionToAlphaTransformationFactoryImpl:: createColorTransformation(const KoColorSpace* srcColorSpace, const KoColorSpace* dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(canBeSource(srcColorSpace)); Q_ASSERT(canBeDestination(dstColorSpace)); if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Integer8BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); +#ifdef HAVE_OPENEXR } else if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Float16BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); +#endif } else if (srcColorSpace->colorModelId() == GrayAColorModelID && srcColorSpace->colorDepthId() == Float32BitsColorDepthID) { return new KoColorConversionGrayAToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else if (srcColorSpace->colorModelId() == LABAColorModelID && srcColorSpace->colorDepthId() == Integer16BitsColorDepthID) { return new KoColorConversionLab16ToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { return new KoColorConversionToAlphaTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } } template bool KoColorConversionToAlphaTransformationFactoryImpl:: conserveColorInformation() const { return false; } template bool KoColorConversionToAlphaTransformationFactoryImpl:: conserveDynamicRange() const { return false; } template class KoColorConversionToAlphaTransformationFactoryImpl; template class KoColorConversionToAlphaTransformationFactoryImpl; #ifdef HAVE_OPENEXR template class KoColorConversionToAlphaTransformationFactoryImpl; #endif template class KoColorConversionToAlphaTransformationFactoryImpl; diff --git a/libs/store/KoXmlReader.cpp b/libs/store/KoXmlReader.cpp index 5e1d00c2fe..7890266284 100644 --- a/libs/store/KoXmlReader.cpp +++ b/libs/store/KoXmlReader.cpp @@ -1,2351 +1,106 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 "KoXmlReader.h" #include "KoXmlNS.h" -/* - This is a memory-efficient DOM implementation for Calligra. See the API - documentation for details. - - IMPORTANT ! - - * When you change this stuff, make sure it DOES NOT BREAK the test suite. - Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights - have been sacrificed for this piece of code, do not let those precious - hours wasted! - - * Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal - memory read/write and any type of leak occurs. If you are not familiar - with Valgrind then RTFM first and come back again later on. - - * The public API shall remain as compatible as QDom. - - * All QDom-compatible methods should behave the same. All QDom-compatible - functions should return the same result. In case of doubt, run - koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h - so that the tests are performed with standard QDom. - - Some differences compared to QDom: - - - DOM tree in KoXmlDocument is read-only, you can not modify it. This is - sufficient for Calligra since the tree is only accessed when loading - a document to the application. For saving the document to XML file, - use KoXmlWriter. - - - Because the dynamic loading and unloading, you have to use the - nodes (and therefore also elements) carefully since the whole API - (just like QDom) is reference-based, not pointer-based. If the - parent node is unloaded from memory, the reference is not valid - anymore and may give unpredictable result. - The easiest way: use the node/element in very short time only. - - - Comment node (like QDomComment) is not implemented as comments are - simply ignored. - - - DTD, entity and entity reference are not handled. Thus, the associated - nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also - not implemented. - - - Attribute mapping node is not implemented. But of course, functions to - query attributes of an element are available. - - - */ - -#include -#include - -#ifndef KOXML_USE_QDOM - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -/* - Use more compact representation of in-memory nodes. - - Advantages: faster iteration, can facilitate real-time compression. - Disadvantages: still buggy, eat slightly more memory. -*/ -#define KOXML_COMPACT - -/* - Use real-time compression. Only works in conjuction with KOXML_COMPACT - above because otherwise the non-compact layout will slow down everything. -*/ -#define KOXML_COMPRESS - - -// prevent mistake, see above -#ifdef KOXML_COMPRESS -#ifndef KOXML_COMPACT -#error Please enable also KOXML_COMPACT -#endif -#endif - -// this is used to quickly get namespaced attribute(s) -typedef QPair KoXmlStringPair; - -class KoQName { -public: - QString nsURI; - QString name; - - explicit KoQName(const QString& nsURI_, const QString& name_) - : nsURI(nsURI_), name(name_) {} - bool operator==(const KoQName& qname) const { - // local name is more likely to differ, so compare that first - return name == qname.name && nsURI == qname.nsURI; - } -}; - -uint qHash(const KoQName& qname) -{ - // possibly add a faster hash function that only includes some trailing - // part of the nsURI - - // in case of doubt, use this: - // return qHash(qname.nsURI)^qHash(qname.name); - return qHash(qname.nsURI)^qHash(qname.name); -} - -static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b) -{ - return a.second == b.second && a.first == b.first; -} - -// Older versions of OpenOffice.org used different namespaces. This function -// does translate the old namespaces into the new ones. -static QString fixNamespace(const QString &nsURI) -{ - static QString office = QString::fromLatin1("http://openoffice.org/2000/office"); - static QString text = QString::fromLatin1("http://openoffice.org/2000/text"); - static QString style = QString::fromLatin1("http://openoffice.org/2000/style"); - static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format"); - static QString table = QString::fromLatin1("http://openoffice.org/2000/table"); - static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing"); - static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle"); - static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg"); - static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart"); - static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d"); - static QString form = QString::fromLatin1("http://openoffice.org/2000/form"); - static QString script = QString::fromLatin1("http://openoffice.org/2000/script"); - static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta"); - static QString config = QString::fromLatin1("http://openoffice.org/2001/config"); - static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation"); - static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest"); - if (nsURI == text) - return KoXmlNS::text; - if (nsURI == style) - return KoXmlNS::style; - if (nsURI == office) - return KoXmlNS::office; - if (nsURI == fo) - return KoXmlNS::fo; - if (nsURI == table) - return KoXmlNS::table; - if (nsURI == drawing) - return KoXmlNS::draw; - if (nsURI == datastyle) - return KoXmlNS::number; - if (nsURI == svg) - return KoXmlNS::svg; - if (nsURI == chart) - return KoXmlNS::chart; - if (nsURI == dr3d) - return KoXmlNS::dr3d; - if (nsURI == form) - return KoXmlNS::form; - if (nsURI == script) - return KoXmlNS::script; - if (nsURI == meta) - return KoXmlNS::meta; - if (nsURI == config) - return KoXmlNS::config; - if (nsURI == pres) - return KoXmlNS::presentation; - if (nsURI == manifest) - return KoXmlNS::manifest; - return nsURI; -} - -// ================================================================== -// -// KoXmlPackedItem -// -// ================================================================== - -// 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems -class KoXmlPackedItem -{ -public: -bool attr: 1; -KoXmlNode::NodeType type: 3; - -#ifdef KOXML_COMPACT -quint32 childStart: 28; -#else -unsigned depth: 28; -#endif - - unsigned qnameIndex; - QString value; - - // it is important NOT to have a copy constructor, so that growth is optimal - // see http://doc.trolltech.com/4.2/containers.html#growth-strategies -#if 0 - KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {} -#endif -}; - -Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE); - -#ifdef KOXML_COMPRESS -static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item) -{ - quint8 flag = item.attr ? 1 : 0; - - s << flag; - s << (quint8) item.type; - s << item.childStart; - s << item.qnameIndex; - s << item.value; - - return s; -} - -static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item) -{ - quint8 flag; - quint8 type; - quint32 child; - QString value; - - s >> flag; - s >> type; - s >> child; - s >> item.qnameIndex; - s >> value; - - item.attr = (flag != 0); - item.type = (KoXmlNode::NodeType) type; - item.childStart = child; - item.value = value; - - return s; -} -#endif - -// ================================================================== -// -// KoXmlPackedDocument -// -// ================================================================== - -#ifdef KOXML_COMPRESS - -#include "KoXmlVector.h" - -// when number of buffered items reach this, compression will start -// small value will give better memory usage at the cost of speed -// bigger value will be better in term of speed, but use more memory -#define ITEMS_FULL (1*256) - -typedef KoXmlVector KoXmlPackedGroup; -#else -typedef QVector KoXmlPackedGroup; -#endif - -// growth strategy: increase every GROUP_GROW_SIZE items -// this will override standard QVector's growth strategy -#define GROUP_GROW_SHIFT 3 -#define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT) - -class KoXmlPackedDocument -{ -public: - bool processNamespace; -#ifdef KOXML_COMPACT - // map given depth to the list of items - QHash groups; -#else - QVector items; -#endif - - QList qnameList; - QString docType; - -private: - QHash qnameHash; - - unsigned cacheQName(const QString& name, const QString& nsURI) { - KoQName qname(nsURI, name); - - const unsigned ii = qnameHash.value(qname, (unsigned)-1); - if (ii != (unsigned)-1) - return ii; - - // not yet declared, so we add it - unsigned i = qnameList.count(); - qnameList.append(qname); - qnameHash.insert(qname, i); - - return i; - } - - QHash valueHash; - QStringList valueList; - - QString cacheValue(const QString& value) { - if (value.isEmpty()) - return 0; - - const unsigned& ii = valueHash[value]; - if (ii > 0) - return valueList[ii]; - - // not yet declared, so we add it - unsigned i = valueList.count(); - valueList.append(value); - valueHash.insert(value, i); - - return valueList[i]; - } - -#ifdef KOXML_COMPACT -public: - const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) { - const KoXmlPackedGroup& group = groups[depth]; - return group[index]; - } - - unsigned itemCount(unsigned depth) { - const KoXmlPackedGroup& group = groups[depth]; - return group.count(); - } - - /* - NOTE: - Function clear, newItem, addElement, addAttribute, addText, - addCData, addProcessing are all related. These are all necessary - for stateful manipulation of the document. See also the calls - to these function from parseDocument(). - - The state itself is defined by the member variables - currentDepth and the groups (see above). - */ - - unsigned currentDepth; - - KoXmlPackedItem& newItem(unsigned depth) { - KoXmlPackedGroup& group = groups[depth]; - -#ifdef KOXML_COMPRESS - KoXmlPackedItem& item = group.newItem(); -#else - // reserve up front - if ((groups.size() % GROUP_GROW_SIZE) == 0) - group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT))); - group.resize(group.count() + 1); - - KoXmlPackedItem& item = group[group.count()-1]; -#endif - - // this is necessary, because intentionally we don't want to have - // a constructor for KoXmlPackedItem - item.attr = false; - item.type = KoXmlNode::NullNode; - item.qnameIndex = 0; - item.childStart = itemCount(depth + 1); - item.value.clear(); - - return item; - } - - void clear() { - currentDepth = 0; - qnameHash.clear(); - qnameList.clear(); - valueHash.clear(); - valueList.clear(); - groups.clear(); - docType.clear(); - - // first node is root - KoXmlPackedItem& rootItem = newItem(0); - rootItem.type = KoXmlNode::DocumentNode; - } - - void finish() { - // won't be needed anymore - qnameHash.clear(); - valueHash.clear(); - valueList.clear(); - - // optimize, see documentation on QVector::squeeze - for (int d = 0; d < groups.count(); ++d) { - KoXmlPackedGroup& group = groups[d]; - group.squeeze(); - } - } - - // in case namespace processing, 'name' contains the prefix already - void addElement(const QString& name, const QString& nsURI) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::ElementNode; - item.qnameIndex = cacheQName(name, nsURI); - - ++currentDepth; - } - - void closeElement() { - --currentDepth; - } - - void addDTD(const QString& dt) { - docType = dt; - } - - void addAttribute(const QString& name, const QString& nsURI, const QString& value) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.attr = true; - item.qnameIndex = cacheQName(name, nsURI); - //item.value = cacheValue( value ); - item.value = value; - } - - void addText(const QString& text) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::TextNode; - item.value = text; - } - - void addCData(const QString& text) { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::CDATASectionNode; - item.value = text; - } - - void addProcessingInstruction() { - KoXmlPackedItem& item = newItem(currentDepth + 1); - item.type = KoXmlNode::ProcessingInstructionNode; - } - -public: - KoXmlPackedDocument(): processNamespace(false), currentDepth(0) { - clear(); - } - -#else - -private: - unsigned elementDepth; - -public: - - KoXmlPackedItem& newItem() { - unsigned count = items.count() + 512; - count = 1024 * (count >> 10); - items.reserve(count); - - items.resize(items.count() + 1); - - // this is necessary, because intentionally we don't want to have - // a constructor for KoXmlPackedItem - KoXmlPackedItem& item = items[items.count()-1]; - item.attr = false; - item.type = KoXmlNode::NullNode; - item.qnameIndex = 0; - item.depth = 0; - - return item; - } - - void addElement(const QString& name, const QString& nsURI) { - // we are going one level deeper - ++elementDepth; - - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::ElementNode; - item.depth = elementDepth; - item.qnameIndex = cacheQName(name, nsURI); - } - - void closeElement() { - // we are going up one level - --elementDepth; - } - - void addDTD(const QString& dt) { - docType = dt; - } - - void addAttribute(const QString& name, const QString& nsURI, const QString& value) { - KoXmlPackedItem& item = newItem(); - - item.attr = true; - item.type = KoXmlNode::NullNode; - item.depth = elementDepth; - item.qnameIndex = cacheQName(name, nsURI); - //item.value = cacheValue( value ); - item.value = value; - } - - void addText(const QString& str) { - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::TextNode; - item.depth = elementDepth + 1; - item.qnameIndex = 0; - item.value = str; - } - - void addCData(const QString& str) { - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::CDATASectionNode; - item.depth = elementDepth + 1; - item.qnameIndex = 0; - item.value = str; - } - - void addProcessingInstruction() { - KoXmlPackedItem& item = newItem(); - - item.attr = false; - item.type = KoXmlNode::ProcessingInstructionNode; - item.depth = elementDepth + 1; - item.qnameIndex = 0; - item.value.clear(); - } - - void clear() { - qnameHash.clear(); - qnameList.clear(); - valueHash.clear(); - valueList.clear(); - items.clear(); - elementDepth = 0; - - KoXmlPackedItem& rootItem = newItem(); - rootItem.attr = false; - rootItem.type = KoXmlNode::DocumentNode; - rootItem.depth = 0; - rootItem.qnameIndex = 0; - } - - void finish() { - qnameHash.clear(); - valueList.clear(); - valueHash.clear(); - items.squeeze(); - } - - KoXmlPackedDocument(): processNamespace(false), elementDepth(0) { - } - -#endif - -}; - -namespace { - - class ParseError { - public: - QString errorMsg; - int errorLine; - int errorColumn; - bool error; - - ParseError() :errorLine(-1), errorColumn(-1), error(false) {} - }; - - void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true); - - // parse one element as if this were a standalone xml document - ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true) - { - doc.clear(); - ParseError error; - xml.readNext(); - while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) { - switch (xml.tokenType()) { - case QXmlStreamReader::StartElement: - parseElement(xml, doc, stripSpaces); - break; - case QXmlStreamReader::DTD: - doc.addDTD(xml.dtdName().toString()); - break; - case QXmlStreamReader::StartDocument: - if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) { - doc.addProcessingInstruction(); - } - break; - case QXmlStreamReader::ProcessingInstruction: - doc.addProcessingInstruction(); - break; - default: - break; - } - xml.readNext(); - } - if (xml.hasError()) { - error.error = true; - error.errorMsg = xml.errorString(); - error.errorColumn = xml.columnNumber(); - error.errorLine = xml.lineNumber(); - } else { - doc.finish(); - } - return error; - } - - void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc) - { - xml.readNext(); - QString ws; - while (!xml.atEnd()) { - switch (xml.tokenType()) { - case QXmlStreamReader::EndElement: - // if an element contains only whitespace, put it in the dom - if (!ws.isEmpty()) { - doc.addText(ws); - } - return; - case QXmlStreamReader::StartElement: - // The whitespaces between > and < are also a text element - if (!ws.isEmpty()) { - doc.addText(ws); - ws.clear(); - } - // Do not strip spaces - parseElement(xml, doc, false); - break; - case QXmlStreamReader::Characters: - if (xml.isCDATA()) { - doc.addCData(xml.text().toString()); - } else if (!xml.isWhitespace()) { - doc.addText(xml.text().toString()); - } else { - ws += xml.text(); - } - break; - case QXmlStreamReader::ProcessingInstruction: - doc.addProcessingInstruction(); - break; - default: - break; - } - xml.readNext(); - } - } - - void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc) - { - xml.readNext(); - QString ws; - bool sawElement = false; - while (!xml.atEnd()) { - switch (xml.tokenType()) { - case QXmlStreamReader::EndElement: - // if an element contains only whitespace, put it in the dom - if (!ws.isEmpty() && !sawElement) { - doc.addText(ws); - } - return; - case QXmlStreamReader::StartElement: - sawElement = true; - // Do strip spaces - parseElement(xml, doc, true); - break; - case QXmlStreamReader::Characters: - if (xml.isCDATA()) { - doc.addCData(xml.text().toString()); - } else if (!xml.isWhitespace()) { - doc.addText(xml.text().toString()); - } else if (!sawElement) { - ws += xml.text(); - } - break; - case QXmlStreamReader::ProcessingInstruction: - doc.addProcessingInstruction(); - break; - default: - break; - } - xml.readNext(); - } - } - - void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces) - { - // Unfortunately MSVC fails using QXmlStreamReader::const_iterator - // so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368 - doc.addElement(xml.qualifiedName().toString(), - fixNamespace(xml.namespaceUri().toString())); - QXmlStreamAttributes attr = xml.attributes(); - for (int a = 0; a < attr.count(); a++) { - doc.addAttribute(attr[a].qualifiedName().toString(), - attr[a].namespaceUri().toString(), - attr[a].value().toString()); - } - if (stripSpaces) - parseElementContentsStripSpaces(xml, doc); - else - parseElementContents(xml, doc); - // reader.tokenType() is now QXmlStreamReader::EndElement - doc.closeElement(); - } -} - - -// ================================================================== -// -// KoXmlNodeData -// -// ================================================================== - -class KoXmlNodeData -{ -public: - - explicit KoXmlNodeData(unsigned long initialRefCount = 1); - ~KoXmlNodeData(); - - // generic properties - KoXmlNode::NodeType nodeType; - bool loaded; - -#ifdef KOXML_COMPACT - unsigned nodeDepth; -#endif - - QString tagName; - QString namespaceURI; - QString prefix; - QString localName; - - void ref() { - ++refCount; - } - void unref() { - if (!--refCount) { - delete this; - } - } - - // type information - QString nodeName() const; - - // for tree and linked-list - KoXmlNodeData* parent; - KoXmlNodeData* prev; - KoXmlNodeData* next; - KoXmlNodeData* first; - KoXmlNodeData* last; - - QString text(); - - // node manipulation - void clear(); - - // attributes - inline void setAttribute(const QString& name, const QString& value); - inline QString attribute(const QString& name, const QString& def) const; - inline bool hasAttribute(const QString& name) const; - inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value); - inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const; - inline bool hasAttributeNS(const QString& nsURI, const QString& name) const; - inline void clearAttributes(); - inline QStringList attributeNames() const; - inline QList< QPair > attributeFullNames() const; - - - // for text and CDATA - QString data() const; - - // reference from within the packed doc - KoXmlPackedDocument* packedDoc; - unsigned long nodeIndex; - - // used when doing on-demand (re)parse - void loadChildren(int depth = 1); - void unloadChildren(); - - void dump(); - - static KoXmlNodeData null; - - // compatibility - void asQDomNode(QDomDocument& ownerDoc) const; - -private: - QHash attr; - QHash attrNS; - QString textData; - // reference counting - unsigned long refCount; - friend class KoXmlElement; -}; - -KoXmlNodeData KoXmlNodeData::null; - - -KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount) - : nodeType(KoXmlNode::NullNode) - , loaded(false) -#ifdef KOXML_COMPACT - , nodeDepth(0) -#endif - , parent(0), prev(0), next(0), first(0), last(0) - , packedDoc(0), nodeIndex(0) - , refCount(initialRefCount) -{ -} - -KoXmlNodeData::~KoXmlNodeData() -{ - clear(); -} - -void KoXmlNodeData::clear() -{ - if (first) - for (KoXmlNodeData* node = first; node ;) { - KoXmlNodeData* next = node->next; - node->unref(); - node = next; - } - - // only document can delete these - // normal nodes don't "own" them - if (nodeType == KoXmlNode::DocumentNode) - delete packedDoc; - - nodeType = KoXmlNode::NullNode; - tagName.clear(); - prefix.clear(); - namespaceURI.clear(); - textData.clear(); - packedDoc = 0; - - attr.clear(); - attrNS.clear(); - - parent = 0; - prev = next = 0; - first = last = 0; - - loaded = false; -} - -QString KoXmlNodeData::text() -{ - QString t; - - loadChildren(); - - KoXmlNodeData* node = first; - while (node) { - switch (node->nodeType) { - case KoXmlNode::ElementNode: - t += node->text(); break; - case KoXmlNode::TextNode: - t += node->data(); break; - case KoXmlNode::CDATASectionNode: - t += node->data(); break; - default: break; - } - node = node->next; - } - - return t; -} - -QString KoXmlNodeData::nodeName() const -{ - switch (nodeType) { - case KoXmlNode::ElementNode: { - QString n(tagName); - if (!prefix.isEmpty()) - n.prepend(':').prepend(prefix); - return n; - } - break; - - case KoXmlNode::TextNode: return QLatin1String("#text"); - case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section"); - case KoXmlNode::DocumentNode: return QLatin1String("#document"); - case KoXmlNode::DocumentTypeNode: return tagName; - - default: return QString(); break; - } - - // should not happen - return QString(); -} - -void KoXmlNodeData::setAttribute(const QString& name, const QString& value) -{ - attr.insert(name, value); -} - -QString KoXmlNodeData::attribute(const QString& name, const QString& def) const -{ - return attr.value(name, def); -} - -bool KoXmlNodeData::hasAttribute(const QString& name) const -{ - return attr.contains(name); -} - -void KoXmlNodeData::setAttributeNS(const QString& nsURI, - const QString& name, const QString& value) -{ - int i = name.indexOf(':'); - if (i != -1) { - QString localName(name.mid(i + 1)); - KoXmlStringPair key(nsURI, localName); - attrNS.insert(key, value); - } -} - -QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name, - const QString& def) const -{ - KoXmlStringPair key(nsURI, name); - return attrNS.value(key, def); -} - -bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const -{ - KoXmlStringPair key(nsURI, name); - return attrNS.contains(key); -} - -void KoXmlNodeData::clearAttributes() -{ - attr.clear(); - attrNS.clear(); -} - -// FIXME how about namespaced attributes ? -QStringList KoXmlNodeData::attributeNames() const -{ - QStringList result; - result = attr.keys(); - - return result; -} - - -QList< QPair > KoXmlNodeData::attributeFullNames() const -{ - QList< QPair > result; - result = attrNS.keys(); - - return result; -} - -QString KoXmlNodeData::data() const -{ - return textData; -} - -#ifdef KOXML_COMPACT - -void KoXmlNodeData::loadChildren(int depth) -{ - // sanity check - if (!packedDoc) return; - - // already loaded ? - if (loaded && (depth <= 1)) return; - - // in case depth is different - unloadChildren(); - - - KoXmlNodeData* lastDat = 0; - - unsigned childStop = 0; - if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) - childStop = packedDoc->itemCount(nodeDepth + 1); - else { - const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); - childStop = next.childStart; - } - - const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); - - for (unsigned i = self.childStart; i < childStop; ++i) { - const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // attribute belongs to this node - if (item.attr) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - setAttributeNS(qname.nsURI, qName, value); - setAttribute(localName, value); - } else - setAttribute(qName, value); - } else { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString nodeName = qname.name; - QString localName; - QString prefix; - - if (packedDoc->processNamespace) { - localName = qname.name; - int di = qname.name.indexOf(':'); - if (di != -1) { - localName = qname.name.mid(di + 1); - prefix = qname.name.left(di); - } - nodeName = localName; - } - - // make a node out of this item - KoXmlNodeData* dat = new KoXmlNodeData; - dat->nodeIndex = i; - dat->packedDoc = packedDoc; - dat->nodeDepth = nodeDepth + 1; - dat->nodeType = item.type; - dat->tagName = nodeName; - dat->localName = localName; - dat->prefix = prefix; - dat->namespaceURI = qname.nsURI; - dat->parent = this; - dat->prev = lastDat; - dat->next = 0; - dat->first = 0; - dat->last = 0; - dat->loaded = false; - dat->textData = (textItem) ? value : QString(); - - // adjust our linked-list - first = (first) ? first : dat; - last = dat; - if (lastDat) - lastDat->next = dat; - lastDat = dat; - - // recursive - if (depth > 1) - dat->loadChildren(depth - 1); - } - } - - loaded = true; -} - -#else - -void KoXmlNodeData::loadChildren(int depth) -{ - // sanity check - if (!packedDoc) return; - - // already loaded ? - if (loaded && (depth <= 1)) return; - - // cause we don't know how deep this node's children already loaded are - unloadChildren(); - - KoXmlNodeData* lastDat = 0; - int nodeDepth = packedDoc->items[nodeIndex].depth; - - for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { - KoXmlPackedItem& item = packedDoc->items[i]; - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // element already outside our depth - if (!item.attr && (item.type == KoXmlNode::ElementNode)) - if (item.depth <= (unsigned)nodeDepth) - break; - - // attribute belongs to this node - if (item.attr && (item.depth == (unsigned)nodeDepth)) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - setAttributeNS(qname.nsURI, qName, value); - setAttribute(localName, value); - } else - setAttribute(qname.name, value); - } - - // the child node - if (!item.attr) { - bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode); - bool ok = (textItem || instruction) ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1); - - ok = (item.depth == (unsigned)nodeDepth + 1); - - if (ok) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - QString value = item.value; - - QString nodeName = qname.name; - QString localName; - QString prefix; - - if (packedDoc->processNamespace) { - localName = qname.name; - int di = qname.name.indexOf(':'); - if (di != -1) { - localName = qname.name.mid(di + 1); - prefix = qname.name.left(di); - } - nodeName = localName; - } - - // make a node out of this item - KoXmlNodeData* dat = new KoXmlNodeData; - dat->nodeIndex = i; - dat->packedDoc = packedDoc; - dat->nodeType = item.type; - dat->tagName = nodeName; - dat->localName = localName; - dat->prefix = prefix; - dat->namespaceURI = qname.nsURI; - dat->count = 1; - dat->parent = this; - dat->prev = lastDat; - dat->next = 0; - dat->first = 0; - dat->last = 0; - dat->loaded = false; - dat->textData = (textItem) ? value : QString(); - - // adjust our linked-list - first = (first) ? first : dat; - last = dat; - if (lastDat) - lastDat->next = dat; - lastDat = dat; - - // recursive - if (depth > 1) - dat->loadChildren(depth - 1); - } - } - } - - loaded = true; -} -#endif - -void KoXmlNodeData::unloadChildren() -{ - // sanity check - if (!packedDoc) return; - - if (!loaded) return; - - if (first) - for (KoXmlNodeData* node = first; node ;) { - KoXmlNodeData* next = node->next; - node->unloadChildren(); - node->unref(); - node = next; - } - - clearAttributes(); - loaded = false; - first = last = 0; -} - -#ifdef KOXML_COMPACT - - -static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, - unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode()) -{ - // sanity check - if (!packedDoc) - return; - - const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); - - unsigned childStop = 0; - if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) - childStop = packedDoc->itemCount(nodeDepth + 1); - else { - const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); - childStop = next.childStart; - } - - // nothing to do here - if (self.type == KoXmlNode::NullNode) - return; - - // create the element properly - if (self.type == KoXmlNode::ElementNode) { - QDomElement element; - - KoQName qname = packedDoc->qnameList[self.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI); - - if (packedDoc->processNamespace) - element = ownerDoc.createElementNS(qname.nsURI, qname.name); - else - element = ownerDoc.createElement(qname.name); - - if ( parentNode.isNull() ) { - ownerDoc.appendChild( element ); - } else { - parentNode.appendChild( element ); - } - // check all subnodes for attributes - for (unsigned i = self.childStart; i < childStop; ++i) { - const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // attribute belongs to this node - if (item.attr) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI ); - QString value = item.value; - - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - element.setAttributeNS(qname.nsURI, qName, value); - element.setAttribute(localName, value); - } else - element.setAttribute(qname.name, value); - } else { - // add it recursively - itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element); - } - } - return; - } - - // create the text node - if (self.type == KoXmlNode::TextNode) { - QString text = self.value; - - // FIXME: choose CDATA when the value contains special characters - QDomText textNode = ownerDoc.createTextNode(text); - if ( parentNode.isNull() ) { - ownerDoc.appendChild( textNode ); - } else { - parentNode.appendChild( textNode ); - } - return; - } - // nothing matches? strange... -} - -void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const -{ - itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex); -} - -#else - -static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, - unsigned nodeIndex, QDomNode parentNode = QDomNode()) -{ - // sanity check - if (!packedDoc) - return; - - KoXmlPackedItem& item = packedDoc->items[nodeIndex]; - - // nothing to do here - if (item.type == KoXmlNode::NullNode) - return; - - // create the element properly - if (item.type == KoXmlNode::ElementNode) { - QDomElement element; - - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI); - - if (packedDoc->processNamespace) - element = ownerDoc.createElementNS(qname.nsURI, qname.name); - else - element = ownerDoc.createElement(qname.name); - - if ( parentNode.isNull() ) { - ownerDoc.appendChild( element ); - } else { - parentNode.appendChild( element ); - } - // check all subnodes for attributes - int nodeDepth = item.depth; - for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { - KoXmlPackedItem& item = packedDoc->items[i]; - bool textItem = (item.type == KoXmlNode::TextNode); - textItem |= (item.type == KoXmlNode::CDATASectionNode); - - // element already outside our depth - if (!item.attr && (item.type == KoXmlNode::ElementNode)) - if (item.depth <= (unsigned)nodeDepth) - break; - - // attribute belongs to this node - if (item.attr && (item.depth == (unsigned)nodeDepth)) { - KoQName qname = packedDoc->qnameList[item.qnameIndex]; - qname.nsURI = fixNamespace(qname.nsURI); - QString value = item.value; - QString prefix; - - QString qName; // with prefix - QString localName; // without prefix, i.e. local name - - localName = qName = qname.name; - int i = qName.indexOf(':'); - if (i != -1) prefix = qName.left(i); - if (i != -1) localName = qName.mid(i + 1); - - if (packedDoc->processNamespace) { - element.setAttributeNS(qname.nsURI, qName, value); - element.setAttribute(localName, value); - } else - element.setAttribute(qname.name, value); - } - - // direct child of this node - if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) { - // add it recursively - itemAsQDomNode(ownerDoc, packedDoc, i, element); - } - } - return; - } - - // create the text node - if (item.type == KoXmlNode::TextNode) { - QString text = item.value; - // FIXME: choose CDATA when the value contains special characters - QDomText textNode = ownerDoc.createTextNode(text); - if ( parentNode.isNull() ) { - ownerDoc.appendChild( textNode ); - } else { - parentNode.appendChild( textNode ); - } - return; - } - - // nothing matches? strange... -} - -void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const -{ - itemAsQDomNode(ownerDoc, packedDoc, nodeIndex); -} - -#endif - -void KoXmlNodeData::dump() -{ - printf("NodeData %p\n", (void*)this); - - printf(" nodeIndex: %d\n", (int)nodeIndex); - printf(" packedDoc: %p\n", (void*)packedDoc); - - printf(" nodeType : %d\n", (int)nodeType); - printf(" tagName: %s\n", qPrintable(tagName)); - printf(" namespaceURI: %s\n", qPrintable(namespaceURI)); - printf(" prefix: %s\n", qPrintable(prefix)); - printf(" localName: %s\n", qPrintable(localName)); - - printf(" parent : %p\n", (void*)parent); - printf(" prev : %p\n", (void*)prev); - printf(" next : %p\n", (void*)next); - printf(" first : %p\n", (void*)first); - printf(" last : %p\n", (void*)last); - - printf(" refCount: %ld\n", refCount); - - if (loaded) - printf(" loaded: TRUE\n"); - else - printf(" loaded: FALSE\n"); -} - - -// ================================================================== -// -// KoXmlNodeData -// -// ================================================================== - -class KoXmlDocumentData : public KoXmlNodeData -{ -public: - - KoXmlDocumentData(unsigned long initialRefCount = 1); - ~KoXmlDocumentData(); - - bool setContent(QXmlStreamReader *reader, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - - KoXmlDocumentType dt; - - bool emptyDocument :1; - // to read the xml with or without spaces - bool stripSpaces :1; -}; - -#define KOXMLDOCDATA(d) static_cast(d) - - -KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount) - : KoXmlNodeData(initialRefCount) - , emptyDocument(true) - , stripSpaces(true) -{ -} - -KoXmlDocumentData::~KoXmlDocumentData() -{ -} - -bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn) -{ - // sanity checks - if (!reader) return false; - - if (nodeType != KoXmlNode::DocumentNode) - return false; - - clear(); - nodeType = KoXmlNode::DocumentNode; - - packedDoc = new KoXmlPackedDocument; - packedDoc->processNamespace = reader->namespaceProcessing(); - - ParseError error = parseDocument(*reader, *packedDoc, stripSpaces); - if (error.error) { - // parsing error has occurred - if (errorMsg) *errorMsg = error.errorMsg; - if (errorLine) *errorLine = error.errorLine; - if (errorColumn) *errorColumn = error.errorColumn; - return false; - } - - // initially load - loadChildren(); - - KoXmlNodeData *typeData = new KoXmlNodeData(0); - typeData->nodeType = KoXmlNode::DocumentTypeNode; - typeData->tagName = packedDoc->docType; - typeData->parent = this; - dt = KoXmlDocumentType(typeData); - - return true; -} - -// ================================================================== -// -// KoXmlNode -// -// ================================================================== - -// Creates a null node -KoXmlNode::KoXmlNode() -{ - d = &KoXmlNodeData::null; - d->ref(); -} - -// Destroys this node -KoXmlNode::~KoXmlNode() -{ - d->unref(); -} - -// Creates a copy of another node -KoXmlNode::KoXmlNode(const KoXmlNode& node) -{ - d = node.d; - d->ref(); -} - -// Creates a node for specific implementation -KoXmlNode::KoXmlNode(KoXmlNodeData* data) -{ - d = data; - data->ref(); -} - -// Creates a shallow copy of another node -KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node) -{ - if (this != &node) { - d->unref(); - d = node.d; - d->ref(); - } - return *this; -} - -// Note: two null nodes are always equal -bool KoXmlNode::operator==(const KoXmlNode& node) const -{ - if (isNull() && node.isNull()) return true; - return(d == node.d); -} - -// Note: two null nodes are always equal -bool KoXmlNode::operator!=(const KoXmlNode& node) const -{ - if (isNull() && !node.isNull()) return true; - if (!isNull() && node.isNull()) return true; - if (isNull() && node.isNull()) return false; - return(d != node.d); -} - -KoXmlNode::NodeType KoXmlNode::nodeType() const -{ - return d->nodeType; -} - -bool KoXmlNode::isNull() const -{ - return d->nodeType == NullNode; -} - -bool KoXmlNode::isElement() const -{ - return d->nodeType == ElementNode; -} - -bool KoXmlNode::isText() const -{ - return (d->nodeType == TextNode) || isCDATASection(); -} - -bool KoXmlNode::isCDATASection() const -{ - return d->nodeType == CDATASectionNode; -} - -bool KoXmlNode::isDocument() const -{ - return d->nodeType == DocumentNode; -} - -bool KoXmlNode::isDocumentType() const -{ - return d->nodeType == DocumentTypeNode; -} - -void KoXmlNode::clear() -{ - d->unref(); - d = new KoXmlNodeData; -} - -QString KoXmlNode::nodeName() const -{ - return d->nodeName(); -} - -QString KoXmlNode::prefix() const -{ - return isElement() ? d->prefix : QString(); -} - -QString KoXmlNode::namespaceURI() const -{ - return isElement() ? d->namespaceURI : QString(); -} - -QString KoXmlNode::localName() const -{ - return isElement() ? d->localName : QString(); -} - -KoXmlDocument KoXmlNode::ownerDocument() const -{ - KoXmlNodeData* node = d; - while (node->parent) node = node->parent; - - if (node->nodeType == DocumentNode) { - return KoXmlDocument(static_cast(node)); - } - return KoXmlDocument(); -} - -KoXmlNode KoXmlNode::parentNode() const -{ - return d->parent ? KoXmlNode(d->parent) : KoXmlNode(); -} - -bool KoXmlNode::hasChildNodes() const -{ - if (isText()) - return false; - - if (!d->loaded) - d->loadChildren(); - - return d->first != 0 ; -} - -int KoXmlNode::childNodesCount() const -{ - if (isText()) - return 0; - - if (!d->loaded) - d->loadChildren(); - - KoXmlNodeData* node = d->first; - int count = 0; - while (node) { - ++count; - node = node->next; - } - - return count; -} - -QStringList KoXmlNode::attributeNames() const -{ - if (!d->loaded) - d->loadChildren(); - - return d->attributeNames(); -} - -QList< QPair > KoXmlNode::attributeFullNames() const -{ - if (!d->loaded) - d->loadChildren(); - - return d->attributeFullNames(); -} - -KoXmlNode KoXmlNode::firstChild() const -{ - if (!d->loaded) - d->loadChildren(); - return d->first ? KoXmlNode(d->first) : KoXmlNode(); -} - -KoXmlElement KoXmlNode::firstChildElement() const -{ - KoXmlElement element; - forEachElement (element, (*this)) { - return element; - } - return KoXmlElement(); -} - -KoXmlNode KoXmlNode::lastChild() const -{ - if (!d->loaded) - d->loadChildren(); - return d->last ? KoXmlNode(d->last) : KoXmlNode(); -} - -KoXmlNode KoXmlNode::nextSibling() const -{ - return d->next ? KoXmlNode(d->next) : KoXmlNode(); -} - -KoXmlNode KoXmlNode::previousSibling() const -{ - return d->prev ? KoXmlNode(d->prev) : KoXmlNode(); -} - -KoXmlNode KoXmlNode::namedItem(const QString& name) const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeName() == name) - return KoXmlNode(node); - } - - // not found - return KoXmlNode(); -} - -KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeType == KoXmlNode::ElementNode - && node->localName == name - && node->namespaceURI == nsURI - ) { - return KoXmlNode(node); - } - } - - // not found - return KoXmlNode(); -} - -KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeType != KoXmlNode::ElementNode) - continue; - if (node->localName == name && node->namespaceURI == nsURI) { - return KoXmlNode(node); - } - bool isPrelude = false; - switch (type) { - case KoXmlTextContentPrelude: - isPrelude = - (node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) || - (node->localName == "forms" && node->namespaceURI == KoXmlNS::office); - break; - } - if (!isPrelude) { - return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here. - } - } - - // not found - return KoXmlNode(); -} - -KoXmlElement KoXmlNode::toElement() const -{ - return isElement() ? KoXmlElement(d) : KoXmlElement(); -} - -KoXmlText KoXmlNode::toText() const -{ - return isText() ? KoXmlText(d) : KoXmlText(); -} - -KoXmlCDATASection KoXmlNode::toCDATASection() const -{ - return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection(); -} - -KoXmlDocument KoXmlNode::toDocument() const -{ - if (isDocument()) { - return KoXmlDocument(static_cast(d)); - } - return KoXmlDocument(); -} - -void KoXmlNode::load(int depth) -{ - d->loadChildren(depth); -} - -void KoXmlNode::unload() -{ - d->unloadChildren(); -} - -void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const -{ - Q_ASSERT(!isDocument()); - d->asQDomNode(ownerDoc); -} - -// ================================================================== -// -// KoXmlElement -// -// ================================================================== - -// Creates an empty element -KoXmlElement::KoXmlElement(): KoXmlNode() -{ -} - -KoXmlElement::~KoXmlElement() -{ -} - -// Creates a shallow copy of another element -KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d) -{ -} - -KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data) -{ -} - -// Copies another element -KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element) -{ - KoXmlNode::operator=(element); - return *this; -} - -bool KoXmlElement::operator== (const KoXmlElement& element) const -{ - if (isNull() || element.isNull()) return false; - return (d == element.d); -} - -bool KoXmlElement::operator!= (const KoXmlElement& element) const -{ - if (isNull() && element.isNull()) return false; - if (isNull() || element.isNull()) return true; - return (d != element.d); -} - -QString KoXmlElement::tagName() const -{ - return isElement() ? d->tagName : QString(); -} - -QString KoXmlElement::text() const -{ - return d->text(); -} - -QString KoXmlElement::attribute(const QString& name) const -{ - if (!isElement()) - return QString(); - - if (!d->loaded) - d->loadChildren(); - - return d->attribute(name, QString()); -} - -QString KoXmlElement::attribute(const QString& name, - const QString& defaultValue) const -{ - if (!isElement()) - return defaultValue; - - if (!d->loaded) - d->loadChildren(); - - return d->attribute(name, defaultValue); -} - -QString KoXmlElement::attributeNS(const QString& namespaceURI, - const QString& localName, const QString& defaultValue) const -{ - if (!isElement()) - return defaultValue; - - if (!d->loaded) - d->loadChildren(); - - KoXmlStringPair key(namespaceURI, localName); - return d->attrNS.value(key, defaultValue); - -// return d->attributeNS( namespaceURI, localName, defaultValue ); -} - -bool KoXmlElement::hasAttribute(const QString& name) const -{ - if (!d->loaded) - d->loadChildren(); - - return isElement() ? d->hasAttribute(name) : false; -} - -bool KoXmlElement::hasAttributeNS(const QString& namespaceURI, - const QString& localName) const -{ - if (!d->loaded) - d->loadChildren(); - - return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false; -} - -// ================================================================== -// -// KoXmlText -// -// ================================================================== - -KoXmlText::KoXmlText(): KoXmlNode() -{ -} - -KoXmlText::~KoXmlText() -{ -} - -KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d) -{ -} - -KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data) -{ -} - -bool KoXmlText::isText() const -{ - return true; -} - -QString KoXmlText::data() const -{ - return d->data(); -} - -KoXmlText& KoXmlText::operator=(const KoXmlText & element) -{ - KoXmlNode::operator=(element); - return *this; -} - -// ================================================================== -// -// KoXmlCDATASection -// -// ================================================================== - -KoXmlCDATASection::KoXmlCDATASection(): KoXmlText() -{ -} - -KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata) - : KoXmlText(cdata) -{ -} - -KoXmlCDATASection::~KoXmlCDATASection() -{ -} - -KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata): - KoXmlText(cdata) -{ -} - -bool KoXmlCDATASection::isCDATASection() const -{ - return true; -} - -KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata) -{ - KoXmlNode::operator=(cdata); - return *this; -} - -// ================================================================== -// -// KoXmlDocumentType -// -// ================================================================== - -KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode() -{ -} - -KoXmlDocumentType::~KoXmlDocumentType() -{ -} - -KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt): - KoXmlNode(dt.d) -{ -} - -QString KoXmlDocumentType::name() const -{ - return nodeName(); -} - -KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt) -{ -} - -KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt) -{ - KoXmlNode::operator=(dt); - return *this; -} - -// ================================================================== -// -// KoXmlDocument -// -// ================================================================== - -KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0)) -{ - KOXMLDOCDATA(d)->emptyDocument = false; - KOXMLDOCDATA(d)->stripSpaces = stripSpaces; -} - -KoXmlDocument::~KoXmlDocument() -{ -} - -KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data) -{ - KOXMLDOCDATA(d)->emptyDocument = true; -} - -// Creates a copy of another document -KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d) -{ -} - -// Creates a shallow copy of another document -KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc) -{ - KoXmlNode::operator=(doc); - return *this; -} - -// Checks if this document and doc are equals -bool KoXmlDocument::operator==(const KoXmlDocument& doc) const -{ - return(d == doc.d); -} - -// Checks if this document and doc are not equals -bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const -{ - return(d != doc.d); -} - -KoXmlElement KoXmlDocument::documentElement() const -{ - if (!d->loaded) - d->loadChildren(); - - for (KoXmlNodeData* node = d->first; node; node = node->next) { - if (node->nodeType == KoXmlNode::ElementNode) { - return KoXmlElement(node); - } - } - - return KoXmlElement(); -} - -KoXmlDocumentType KoXmlDocument::doctype() const -{ - return KOXMLDOCDATA(d)->dt; -} - -QString KoXmlDocument::nodeName() const -{ - return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString(); -} - -void KoXmlDocument::clear() -{ - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->emptyDocument = false; - d = dat; -} - -namespace { - /* Use an entity resolver that ignores undefined entities and simply - returns an empty string for them. - */ - class DumbEntityResolver : public QXmlStreamEntityResolver { - public: - QString resolveUndeclaredEntity ( const QString &) override { return ""; } - }; - -} - -bool KoXmlDocument::setContent(QXmlStreamReader *reader, - QString* errorMsg, int* errorLine, int* errorColumn) -{ - if (d->nodeType != KoXmlNode::DocumentNode) { - const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->nodeType = KoXmlNode::DocumentNode; - dat->stripSpaces = stripSpaces; - d = dat; - } - - const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn); - - return result; -} - -// no namespace processing -bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg, - int* errorLine, int* errorColumn) -{ - return setContent(device, false, errorMsg, errorLine, errorColumn); -} - -bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing, - QString* errorMsg, int* errorLine, int* errorColumn) -{ - if (d->nodeType != KoXmlNode::DocumentNode) { - const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->nodeType = KoXmlNode::DocumentNode; - dat->stripSpaces = stripSpaces; - d = dat; - } - - if (!device->isOpen()) device->open(QIODevice::ReadOnly); - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(namespaceProcessing); - DumbEntityResolver entityResolver; - reader.setEntityResolver(&entityResolver); - - const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); - - return result; -} - -bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing, - QString *errorMsg, int *errorLine, int *errorColumn) -{ - QBuffer buffer; - buffer.setData(text); - return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn); -} - -bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing, - QString *errorMsg, int *errorLine, int *errorColumn) -{ - if (d->nodeType != KoXmlNode::DocumentNode) { - const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; - d->unref(); - KoXmlDocumentData *dat = new KoXmlDocumentData; - dat->nodeType = KoXmlNode::DocumentNode; - dat->stripSpaces = stripSpaces; - d = dat; - } - - QXmlStreamReader reader(text); - reader.setNamespaceProcessing(namespaceProcessing); - DumbEntityResolver entityResolver; - reader.setEntityResolver(&entityResolver); - - const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); - - return result; -} - -bool KoXmlDocument::setContent(const QString& text, - QString *errorMsg, int *errorLine, int *errorColumn) -{ - return setContent(text, false, errorMsg, errorLine, errorColumn); -} - -void KoXmlDocument::setWhitespaceStripping(bool stripSpaces) -{ - KOXMLDOCDATA(d)->stripSpaces = stripSpaces; -} - +#include +#include +#include -#endif // ================================================================== // // functions in KoXml namespace // // ================================================================== KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName) { -#ifdef KOXML_USE_QDOM // David's solution for namedItemNS, only for QDom stuff KoXmlNode n = node.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement() && n.localName() == localName && n.namespaceURI() == nsURI) return n.toElement(); } return KoXmlElement(); -#else - return node.namedItemNS(nsURI, localName).toElement(); -#endif } KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type) { -#ifdef KOXML_USE_QDOM -Q_ASSERT(false); + Q_UNUSED(type) return namedItemNS(node, nsURI, localName); -#else - return node.namedItemNS(nsURI, localName, type).toElement(); -#endif } void KoXml::load(KoXmlNode& node, int depth) { -#ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand loading Q_UNUSED(node); Q_UNUSED(depth); -#else - node.load(depth); -#endif } void KoXml::unload(KoXmlNode& node) { -#ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand unloading Q_UNUSED(node); -#else - node.unload(); -#endif } int KoXml::childNodesCount(const KoXmlNode& node) { -#ifdef KOXML_USE_QDOM return node.childNodes().count(); -#else - // compatibility function, because no need to implement - // a class like QDomNodeList - return node.childNodesCount(); -#endif } QStringList KoXml::attributeNames(const KoXmlNode& node) { -#ifdef KOXML_USE_QDOM QStringList result; QDomNamedNodeMap attrMap = node.attributes(); for (int i = 0; i < attrMap.count(); ++i) result += attrMap.item(i).toAttr().name(); return result; -#else - // compatibility function, because no need to implement - // a class like QDomNamedNodeMap - return node.attributeNames(); -#endif } void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node) { Q_ASSERT(!node.isDocument()); -#ifdef KOXML_USE_QDOM - ownerDoc.appendChild(ownerDoc.importNode(node)); -#else - node.asQDomNode(ownerDoc); -#endif + ownerDoc.appendChild(ownerDoc.importNode(node, true)); } void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element) { KoXml::asQDomNode(ownerDoc, element); } QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document) { -#ifdef KOXML_USE_QDOM return document; -#else - QDomDocument qdoc( document.nodeName() ); - if ( document.hasChildNodes() ) { - for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) { - KoXml::asQDomNode(qdoc, n); - } - } - return qdoc; -#endif } -bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device, - bool namespaceProcessing, QString* errorMsg, int* errorLine, - int* errorColumn) +bool KoXml::setDocument(KoXmlDocument& doc, QIODevice *device, + bool namespaceProcessing, + QString *errorMsg, int *errorLine, int *errorColumn) { - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(namespaceProcessing); - bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn); + bool result = doc.setContent(device, namespaceProcessing, errorMsg, errorLine, errorColumn); return result; } diff --git a/libs/store/KoXmlReader.h b/libs/store/KoXmlReader.h index 1a39f6762e..aa0f888348 100644 --- a/libs/store/KoXmlReader.h +++ b/libs/store/KoXmlReader.h @@ -1,451 +1,181 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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_XMLREADER_H #define KO_XMLREADER_H -// KOXML_USE_QDOM is defined there #include "KoXmlReaderForward.h" #include "kritastore_export.h" #include #include class QIODevice; -#ifndef KOXML_USE_QDOM - -class QXmlStreamReader; - -class KoXmlNodeData; -class KoXmlDocumentData; -class QDomDocument; -class QStringList; /** * The office-text-content-prelude type. */ enum KoXmlNamedItemType { KoXmlTextContentPrelude ///< office-text-content-prelude //KoXmlTextContentMain, ///< office-text-content-main //KoXmlTextContentEpilogue ///< office-text-content-epilogue }; -/** -* KoXmlNode represents a node in a DOM tree. -* -* KoXmlNode is a base class for KoXmlElement, KoXmlText. -* Often, these subclasses are used for getting the data instead of KoXmlNode. -* However, as base class, KoXmlNode is very helpful when for example iterating -* all child nodes within one parent node. -* -* KoXmlNode implements an explicit sharing, a node shares its data with -* other copies (if exist). -* -* XXX: DO NOT ADD CONVENIENCE API HERE BECAUSE THIS CLASS MUST REMAIN COMPATIBLE WITH QDOMNODE! -* -* @author Ariya Hidayat -*/ -class KRITASTORE_EXPORT KoXmlNode -{ -public: - - enum NodeType { - NullNode = 0, - ElementNode, - TextNode, - CDATASectionNode, - ProcessingInstructionNode, - DocumentNode, - DocumentTypeNode - }; - - KoXmlNode(); - KoXmlNode(const KoXmlNode& node); - KoXmlNode& operator=(const KoXmlNode& node); - bool operator== (const KoXmlNode&) const; - bool operator!= (const KoXmlNode&) const; - virtual ~KoXmlNode(); - - virtual KoXmlNode::NodeType nodeType() const; - virtual bool isNull() const; - virtual bool isElement() const; - virtual bool isText() const; - virtual bool isCDATASection() const; - virtual bool isDocument() const; - virtual bool isDocumentType() const; - - virtual void clear(); - KoXmlElement toElement() const; - KoXmlText toText() const; - KoXmlCDATASection toCDATASection() const; - KoXmlDocument toDocument() const; - - virtual QString nodeName() const; - virtual QString namespaceURI() const; - virtual QString prefix() const; - virtual QString localName() const; - - KoXmlDocument ownerDocument() const; - KoXmlNode parentNode() const; - - bool hasChildNodes() const; - KoXmlNode firstChild() const; - KoXmlNode lastChild() const; - KoXmlNode nextSibling() const; - KoXmlNode previousSibling() const; - - KoXmlElement firstChildElement() const; - - // equivalent to node.childNodes().count() if node is a QDomNode instance - int childNodesCount() const; - - // workaround to get and iterate over all attributes - QStringList attributeNames() const; - QList< QPair > attributeFullNames() const; - - KoXmlNode namedItem(const QString& name) const; - KoXmlNode namedItemNS(const QString& nsURI, const QString& name) const; - KoXmlNode namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const; - - /** - * Loads all child nodes (if any) of this node. Normally you do not need - * to call this function as the child nodes will be automatically - * loaded when necessary. - */ - void load(int depth = 1); - - /** - * Releases all child nodes of this node. - */ - void unload(); - - // compatibility - /** - * @internal do not call directly - * Use KoXml::asQDomDocument(), KoXml::asQDomElement() or KoXml::asQDomNode() instead - */ - void asQDomNode(QDomDocument& ownerDoc) const; - -protected: - KoXmlNodeData* d; - explicit KoXmlNode(KoXmlNodeData*); -}; - -/** -* KoXmlElement represents a tag element in a DOM tree. -* -* KoXmlElement holds information about an XML tag, along with its attributes. -* -* @author Ariya Hidayat -*/ - -class KRITASTORE_EXPORT KoXmlElement: public KoXmlNode -{ -public: - KoXmlElement(); - KoXmlElement(const KoXmlElement& element); - KoXmlElement& operator=(const KoXmlElement& element); - ~KoXmlElement() override; - bool operator== (const KoXmlElement&) const; - bool operator!= (const KoXmlElement&) const; - - QString tagName() const; - QString text() const; - - QString attribute(const QString& name) const; - QString attribute(const QString& name, const QString& defaultValue) const; - QString attributeNS(const QString& namespaceURI, const QString& localName, - const QString& defaultValue = QString()) const; - bool hasAttribute(const QString& name) const; - bool hasAttributeNS(const QString& namespaceURI, const QString& localName) const; - -private: - friend class KoXmlNode; - friend class KoXmlDocument; - explicit KoXmlElement(KoXmlNodeData*); -}; - -/** -* KoXmlText represents a text in a DOM tree. -* @author Ariya Hidayat -*/ -class KRITASTORE_EXPORT KoXmlText: public KoXmlNode -{ -public: - KoXmlText(); - KoXmlText(const KoXmlText& text); - KoXmlText& operator=(const KoXmlText& text); - ~KoXmlText() override; - - QString data() const; - bool isText() const override; - -private: - friend class KoXmlNode; - friend class KoXmlCDATASection; - friend class KoXmlDocument; - explicit KoXmlText(KoXmlNodeData*); -}; - -/** -* KoXmlCDATASection represents a CDATA section in a DOM tree. -* @author Ariya Hidayat -*/ -class KRITASTORE_EXPORT KoXmlCDATASection: public KoXmlText -{ -public: - KoXmlCDATASection(); - KoXmlCDATASection(const KoXmlCDATASection& cdata); - KoXmlCDATASection& operator=(const KoXmlCDATASection& cdata); - ~KoXmlCDATASection() override; - - bool isCDATASection() const override; - -private: - friend class KoXmlNode; - friend class KoXmlDocument; - explicit KoXmlCDATASection(KoXmlNodeData*); -}; - -/** -* KoXmlDocumentType represents the DTD of the document. At the moment, -* it can used only to get the document type, i.e. no support for -* entities etc. -* -* @author Ariya Hidayat -*/ - -class KRITASTORE_EXPORT KoXmlDocumentType: public KoXmlNode -{ -public: - KoXmlDocumentType(); - KoXmlDocumentType(const KoXmlDocumentType&); - KoXmlDocumentType& operator=(const KoXmlDocumentType&); - ~KoXmlDocumentType() override; - - QString name() const; - -private: - friend class KoXmlNode; - friend class KoXmlDocument; - friend class KoXmlDocumentData; - explicit KoXmlDocumentType(KoXmlNodeData*); -}; - - -/** -* KoXmlDocument represents an XML document, structured in a DOM tree. -* -* KoXmlDocument is designed to be memory efficient. Unlike QDomDocument from -* Qt's XML module, KoXmlDocument does not store all nodes in the DOM tree. -* Some nodes will be loaded and parsed on-demand only. -* -* KoXmlDocument is read-only, you can not modify its content. -* -* @author Ariya Hidayat -*/ - -class KRITASTORE_EXPORT KoXmlDocument: public KoXmlNode -{ -public: - explicit KoXmlDocument(bool stripSpaces = false); - KoXmlDocument(const KoXmlDocument& node); - KoXmlDocument& operator=(const KoXmlDocument& node); - bool operator==(const KoXmlDocument&) const; - bool operator!=(const KoXmlDocument&) const; - ~KoXmlDocument() override; - - KoXmlElement documentElement() const; - - KoXmlDocumentType doctype() const; - - QString nodeName() const override; - void clear() override; - - bool setContent(QIODevice* device, bool namespaceProcessing, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - bool setContent(QIODevice* device, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - bool setContent(QXmlStreamReader *reader, - QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); - bool setContent(const QByteArray& text, bool namespaceProcessing, - QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); - bool setContent(const QString& text, bool namespaceProcessing, - QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); - - // no namespace processing - bool setContent(const QString& text, - QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); - /** - * Change the way an XMLDocument will be read: - * if stripSpaces = true then a will only have one child - * if stripSpaces = false then a will have 3 children. - */ - void setWhitespaceStripping(bool stripSpaces); - -private: - friend class KoXmlNode; - explicit KoXmlDocument(KoXmlDocumentData*); -}; - -#endif // KOXML_USE_QDOM - /** * This namespace contains a few convenience functions to simplify code using QDom * (when loading OASIS documents, in particular). * * To find the child element with a given name, use KoXml::namedItemNS. * * To find all child elements with a given name, use * QDomElement e; * forEachElement( e, parent ) * { * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) * { * ... * } * } * Note that this means you don't ever need to use QDomNode nor toElement anymore! * Also note that localName is the part without the prefix, this is the whole point * of namespace-aware methods. * * To find the attribute with a given name, use QDomElement::attributeNS. * * Do not use getElementsByTagNameNS, it's recursive (which is never needed in Calligra). * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. * * @author David Faure */ namespace KoXml { /** * A namespace-aware version of QDomNode::namedItem(), * which also takes care of casting to a QDomElement. * * Use this when a domelement is known to have only *one* child element * with a given tagname. * * Note: do *NOT* use getElementsByTagNameNS, it's recursive! */ KRITASTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName); /** * A namespace-aware version of QDomNode::namedItem(). * which also takes care of casting to a QDomElement. * * Use this when you like to return the first or an invalid * KoXmlElement with a known type. * * This is an optimized version of the namedItemNS above to * give fast access to certain sections of the document using * the office-text-content-prelude condition as @a KoXmlNamedItemType . */ KRITASTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type); /** * Explicitly load child nodes of specified node, up to given depth. * This function has no effect if QDom is used. */ KRITASTORE_EXPORT void load(KoXmlNode& node, int depth = 1); /** * Unload child nodes of specified node. * This function has no effect if QDom is used. */ KRITASTORE_EXPORT void unload(KoXmlNode& node); /** * Get the number of child nodes of specified node. */ KRITASTORE_EXPORT int childNodesCount(const KoXmlNode& node); /** * Return the name of all attributes of specified node. */ KRITASTORE_EXPORT QStringList attributeNames(const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p node (and its children) are added to ownerDoc. * * NOTE: * - If ownerDoc is not empty, this may fail, @see QDomDocument * - @p node must not be a KoXmlDocument, use asQDomDocument() - * + * * @see asQDomDocument, asQDomElement */ KRITASTORE_EXPORT void asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p element (and its children) is added to ownerDoc. - * + * * NOTE: If ownerDoc is not empty, this may fail, @see QDomDocument * */ KRITASTORE_EXPORT void asQDomElement(QDomDocument& ownerDoc, const KoXmlElement& element); /** * Converts the whole @p document into a QDomDocument - * If KOXML_USE_QDOM is defined, just returns @p document */ KRITASTORE_EXPORT QDomDocument asQDomDocument(const KoXmlDocument& document); /* * Load an XML document from specified device to a document. You can of * course use it with QFile (which inherits QIODevice). * This is much more memory efficient than standard QDomDocument::setContent * because the data from the device is buffered, unlike * QDomDocument::setContent which just loads everything in memory. * * Note: it is assumed that the XML uses UTF-8 encoding. */ KRITASTORE_EXPORT bool setDocument(KoXmlDocument& doc, QIODevice* device, bool namespaceProcessing, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); } /** * \def forEachElement( elem, parent ) * \brief Loop through all child elements of \parent. * This convenience macro is used to implement the forEachElement loop. * The \elem parameter is a name of a QDomElement variable and the \parent * is the name of the parent element. For example: * * QDomElement e; * forEachElement( e, parent ) * { * qDebug() << e.localName() << " element found."; * ... * } */ #define forEachElement( elem, parent ) \ for ( KoXmlNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \ if ( ( elem = _node.toElement() ).isNull() ) {} else #endif // KO_XMLREADER_H diff --git a/libs/store/KoXmlReaderForward.h b/libs/store/KoXmlReaderForward.h index f49a7c1cb9..a615d28cd0 100644 --- a/libs/store/KoXmlReaderForward.h +++ b/libs/store/KoXmlReaderForward.h @@ -1,49 +1,37 @@ /* 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 - -#ifdef KOXML_USE_QDOM +#define KOXML_USE_QDOM #include typedef QDomNode KoXmlNode; typedef QDomElement KoXmlElement; typedef QDomText KoXmlText; typedef QDomCDATASection KoXmlCDATASection; typedef QDomDocumentType KoXmlDocumentType; typedef QDomDocument KoXmlDocument; -#else - -class KoXmlElement; -class KoXmlNode; -class KoXmlText; -class KoXmlCDATASection; -class KoXmlDocumentType; -class KoXmlDocument; - -#endif #endif // KOXMLREADERFORWARD_H diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 22abf325f4..28ac7e70ee 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1764 +1,1769 @@ /* This file is part of the Krita project * * 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 "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; /********************************************************** * * KisDocument * **********************************************************/ namespace { class DocumentProgressProxy : public KoProgressProxy { public: KisMainWindow *m_mainWindow; DocumentProgressProxy(KisMainWindow *mainWindow) : m_mainWindow(mainWindow) { } ~DocumentProgressProxy() override { // signal that the job is done setValue(-1); } int maximum() const override { return 100; } void setValue(int value) override { if (m_mainWindow) { m_mainWindow->slotProgress(value); } } void setRange(int /*minimum*/, int /*maximum*/) override { } void setFormat(const QString &/*format*/) override { } }; } //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private() : docInfo(0), progressUpdater(0), progressProxy(0), importExportManager(0), isImporting(false), isExporting(false), password(QString()), modifiedAfterAutosave(false), isAutosaving(false), backupFile(true), doNotSaveExtDoc(false), undoStack(0), m_saveOk(false), m_waitForSave(false), m_duringSaveAs(false), m_bAutoDetectedMime(false), modified(false), readwrite(true), disregardAutosaveFailure(false), nserver(0), macroNestDepth(0), imageIdleWatcher(2000 /*ms*/), suppressProgress(false), fileProgressProxy(0), savingLock(&savingMutex) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo; KoProgressUpdater *progressUpdater; KoProgressProxy *progressProxy; KoUnit unit; KisImportExportManager *importExportManager; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving bool isImporting; bool isExporting; // File --> Import/Export vs File --> Open/Save QString password; // The password used to encrypt an encrypted document QTimer autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay {300}; // in seconds, 0 to disable. bool modifiedAfterAutosave; bool isAutosaving; bool backupFile; bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents KUndo2Stack *undoStack; KisGuidesConfig guidesConfig; QUrl m_originalURL; // for saveAs QString m_originalFilePath; // for saveAs bool m_saveOk; bool m_waitForSave; bool m_duringSaveAs; bool m_bAutoDetectedMime; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QEventLoop m_eventLoop; QMutex savingMutex; bool modified; bool readwrite; QDateTime firstMod; QDateTime lastMod; bool disregardAutosaveFailure; KisNameServer *nserver; qint32 macroNestDepth; KisImageSP image; KisImageSP savingImage; KisNodeSP preActivatedNode; KisShapeController* shapeController; KoShapeController* koShapeController; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; bool suppressProgress; KoProgressProxy* fileProgressProxy; QList assistants; KisGridConfig gridConfig; StdLockableWrapper savingLock; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class SafeSavingLocker; }; class KisDocument::Private::SafeSavingLocker { public: SafeSavingLocker(KisDocument::Private *_d, KisDocument *document) : d(_d) , m_document(document) , m_locked(false) , m_imageLock(d->image, true) { const int realAutoSaveInterval = KisConfig().autoSaveInterval(); const int emergencyAutoSaveInterval = 10; // sec /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, d->savingLock) < 0; if (!m_locked) { if (d->isAutosaving) { d->disregardAutosaveFailure = true; if (realAutoSaveInterval) { m_document->setAutoSaveDelay(emergencyAutoSaveInterval); } } else { d->image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, d->savingLock) < 0; } } if (m_locked) { d->disregardAutosaveFailure = false; } } ~SafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); d->savingLock.unlock(); const int realAutoSaveInterval = KisConfig().autoSaveInterval(); m_document->setAutoSaveDelay(realAutoSaveInterval); } } bool successfullyLocked() const { return m_locked; } private: KisDocument::Private *d; KisDocument *m_document; bool m_locked; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private()) { d->undoStack = new UndoStack(this); d->undoStack->setParent(this); d->importExportManager = new KisImportExportManager(this); d->importExportManager->setProgresUpdater(d->progressUpdater); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); KisConfig cfg; setAutoSaveDelay(cfg.autoSaveInterval()); setObjectName(newObjectName()); d->docInfo = new KoDocumentInfo(this); d->firstMod = QDateTime::currentDateTime(); d->lastMod = QDateTime::currentDateTime(); // preload the krita resources KisResourceServerProvider::instance(); d->nserver = new KisNameServer(1); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); undoStack()->setUndoLimit(KisConfig().undoStackLimit()); connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int))); setBackupFile(KisConfig().backupFile()); } KisDocument::~KisDocument() { /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer.disconnect(this); d->autoSaveTimer.stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "exportDocument" << _url.toDisplayString() << "is autosaving" << d->isAutosaving; bool ret; d->isExporting = true; // // Preserve a lot of state here because we need to restore it in order to // be able to fake a File --> Export. Can't do this in saveFile() because, // for a start, KParts has already set url and m_file and because we need // to restore the modified flag etc. and don't want to put a load on anyone // reimplementing saveFile() (Note: importDocument() and exportDocument() // will remain non-virtual). // QUrl oldURL = url(); QString oldFile = localFilePath(); //qDebug() << "\toldUrl" << oldURL << "oldFile" << oldFile << "export url" << _url; bool wasModified = isModified(); // save... ret = saveAs(_url, exportConfiguration); // // This is sooooo hacky :( // Hopefully we will restore enough state. // dbgUI << "Restoring KisDocument state to before export"; // always restore url & m_file regardless of failure or success //qDebug() << "\tafter saveAs: url" << url() << "local file path" << localFilePath(); setUrl(oldURL); setLocalFilePath(oldFile); //qDebug() << "\tafter restoring: url" << url() << "local file path" << localFilePath(); // on successful export we need to restore modified etc. too // on failed export, mimetype/modified hasn't changed anyway if (ret) { setModified(wasModified); } d->isExporting = false; return ret; } bool KisDocument::saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "saveAs" << url; if (!url.isValid() || !url.isLocalFile()) { errKrita << "saveAs: Malformed URL " << url.url() << endl; return false; } d->m_duringSaveAs = true; d->m_originalURL = d->m_url; d->m_originalFilePath = d->m_file; d->m_url = url; // Store where to upload in saveToURL d->m_file = d->m_url.toLocalFile(); bool result = save(exportConfiguration); // Save local file and upload local file if (!result) { d->m_url = d->m_originalURL; d->m_file = d->m_originalFilePath; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); } return result; } bool KisDocument::save(KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath(); d->m_saveOk = false; if (d->m_file.isEmpty()) { // document was created empty d->m_file = d->m_url.toLocalFile(); } updateEditingTime(true); setFileProgressProxy(); setUrl(url()); bool ok = saveFile(localFilePath(), exportConfiguration); clearFileProgressProxy(); if (ok) { setModified( false ); emit completed(); d->m_saveOk = true; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); return true; // Nothing to do } else { emit canceled(QString()); } return false; } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); if (!prepareLocksForSaving()) { return byteArray; } if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } unlockAfterSaving(); return byteArray; } bool KisDocument::isInSaving() const { std::unique_lock> l(d->savingLock, std::try_to_lock); return !l.owns_lock(); } bool KisDocument::saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration) { if (!prepareLocksForSaving()) { return false; } // Unset the error message setErrorMessage(""); // Save it to be able to restore it after a failed save const bool wasModified = isModified(); bool ret = false; bool suppressErrorDialog = fileBatchMode(); KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; //qDebug() << "saveFile" << localFilePath() << QFileInfo(localFilePath()).exists() << !QFileInfo(localFilePath()).isWritable(); if (QFileInfo(localFilePath()).exists() && !QFileInfo(localFilePath()).isWritable()) { setErrorMessage(i18n("%1 cannot be written to. Please save under a different name.", localFilePath())); } else { // The output format is set by KisMainWindow, and by openFile QByteArray outputMimeType = d->outputMimeType; if (outputMimeType.isEmpty()) { outputMimeType = d->outputMimeType = nativeFormatMimeType(); } //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType; if (d->backupFile) { Q_ASSERT(url().isLocalFile()); KBackup::backupFile(url().toLocalFile()); } qApp->processEvents(); setFileProgressUpdater(i18n("Saving Document")); //qDebug() << "saving to tempory file" << tempororaryFileName; status = d->importExportManager->exportDocument(localFilePath(), filePath, outputMimeType, !d->isExporting , exportConfiguration); ret = (status == KisImportExportFilter::OK); suppressErrorDialog = (fileBatchMode() || isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph); //qDebug() << "Export status was" << status; if (ret) { if (!d->isAutosaving && !d->suppressProgress) { QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack"); updater->setProgress(0); d->undoStack->setClean(); updater->setProgress(100); } else { d->undoStack->setClean(); } if (errorMessage().isEmpty()) { if (!isAutosaving()) { removeAutoSaveFiles(); } } else { ret = false; qWarning() << "Error while saving:" << errorMessage(); } // Restart the autosave timer // (we don't want to autosave again 2 seconds after a real save) if (!isAutosaving()) { setAutoSaveDelay(d->autoSaveDelay); } d->mimeType = outputMimeType; } } if (!ret) { if (!suppressErrorDialog) { if (errorMessage().isEmpty()) { setErrorMessage(KisImportExportFilter::conversionStatusString(status)); } if (errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", filePath)); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage())); } } // couldn't save file so this new URL is invalid // FIXME: we should restore the current document's true URL instead of // setting it to nothing otherwise anything that depends on the URL // being correct will not work (i.e. the document will be called // "Untitled" which may not be true) // // Update: now the URL is restored in KisMainWindow but really, this // should still be fixed in KisDocument/KParts (ditto for file). // We still resetURL() here since we may or may not have been called // by KisMainWindow - Clarence resetURL(); // As we did not save, restore the "was modified" status setModified(wasModified); } emit sigSavingFinished(); clearFileProgressUpdater(); unlockAfterSaving(); return ret; } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } void KisDocument::setOutputMimeType(const QByteArray & mimeType) { d->outputMimeType = mimeType; } QByteArray KisDocument::outputMimeType() const { return d->outputMimeType; } bool KisDocument::fileBatchMode() const { return d->importExportManager->batchMode(); } void KisDocument::setFileBatchMode(const bool batchMode) { d->importExportManager->setBatchMode(batchMode); } bool KisDocument::isImporting() const { return d->isImporting; } bool KisDocument::isExporting() const { return d->isExporting; } void KisDocument::slotAutoSave() { //qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath(); if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) { + bool batchmode = d->importExportManager->batchMode(); + d->importExportManager->setBatchMode(true); + qApp->setOverrideCursor(Qt::BusyCursor); connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); emit statusBarMessage(i18n("Autosaving...")); d->isAutosaving = true; QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QByteArray mimetype = d->outputMimeType; d->outputMimeType = nativeFormatMimeType(); bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName)); d->outputMimeType = mimetype; if (ret) { d->modifiedAfterAutosave = false; d->autoSaveTimer.stop(); // until the next change } + qApp->restoreOverrideCursor(); + d->importExportManager->setBatchMode(batchmode); d->isAutosaving = false; emit clearStatusBarMessage(); disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); if (!ret && !d->disregardAutosaveFailure) { emit statusBarMessage(i18n("Error during autosave! Partition full?")); } } } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setAutoSaveDelay(d->autoSaveDelay); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay; d->autoSaveDelay = delay; if (isReadWrite() && d->autoSaveDelay > 0) { d->autoSaveTimer.start(d->autoSaveDelay * 1000); } else { d->autoSaveTimer.stop(); } } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); if (path.isEmpty()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); d->isImporting = true; // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } d->isImporting = false; return ret; } bool KisDocument::openUrl(const QUrl &_url, KisDocument::OpenUrlFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened) { resetURL(); // Force save to act like 'Save As' setReadWrite(true); // enable save button setModified(true); } else { if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("dialog-warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; setFileProgressUpdater(i18n("Opening Document")); KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } clearFileProgressUpdater(); return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); if (!d->suppressProgress && d->progressUpdater) { QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack"); updater->setProgress(0); undoStack()->clear(); updater->setProgress(100); clearFileProgressUpdater(); } else { undoStack()->clear(); } return true; } KoProgressUpdater *KisDocument::progressUpdater() const { return d->progressUpdater; } void KisDocument::setProgressProxy(KoProgressProxy *progressProxy) { d->progressProxy = progressProxy; } KoProgressProxy* KisDocument::progressProxy() const { if (!d->progressProxy) { KisMainWindow *mainWindow = 0; if (KisPart::instance()->mainwindowCount() > 0) { mainWindow = KisPart::instance()->mainWindows()[0]; } d->progressProxy = new DocumentProgressProxy(mainWindow); } return d->progressProxy; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified() { d->modified = true; } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; if (documentInfo()) { c = documentInfo()->aboutInfo("title"); } const QString _url(url().fileName()); if (!c.isEmpty() && !_url.isEmpty()) { c = QString("%1 - %2").arg(c).arg(_url); } else if (c.isEmpty()) { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles() { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } } void KisDocument::setBackupFile(bool saveBackup) { d->backupFile = saveBackup; } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackIndexChanged(int idx) { // even if the document was already modified, call setModified to re-start autosave timer setModified(idx != d->undoStack->cleanIndex()); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setFileProgressProxy(); setUrl(d->m_url); ret = openFile(); clearFileProgressProxy(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisConfig cfg; KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); if (name != i18n("Unnamed") && !name.isEmpty()) { setUrl(QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation) + '/' + name + ".kra")); } documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } vKisNodeSP KisDocument::activeNodes() const { vKisNodeSP nodes; Q_FOREACH (KisView *v, KisPart::instance()->views()) { if (v->document() == this && v->viewManager()) { KisNodeSP activeNode = v->viewManager()->activeNode(); if (activeNode && !nodes.contains(activeNode)) { if (activeNode->inherits("KisMask")) { activeNode = activeNode->parent(); } nodes.append(activeNode); } } } return nodes; } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList value) { d->assistants = value; } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } void KisDocument::setFileProgressUpdater(const QString &text) { d->suppressProgress = d->importExportManager->batchMode(); if (!d->suppressProgress) { d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded); d->progressUpdater->start(100, text); d->importExportManager->setProgresUpdater(d->progressUpdater); if (KisPart::instance()->currentMainwindow()) { connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled())); } } } void KisDocument::clearFileProgressUpdater() { if (!d->suppressProgress && d->progressUpdater) { if (KisPart::instance()->currentMainwindow()) { disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled())); disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); } delete d->progressUpdater; d->importExportManager->setProgresUpdater(0); d->progressUpdater = 0; } } void KisDocument::setFileProgressProxy() { if (!d->progressProxy && !d->importExportManager->batchMode()) { d->fileProgressProxy = progressProxy(); } else { d->fileProgressProxy = 0; } } void KisDocument::clearFileProgressProxy() { if (d->fileProgressProxy) { setProgressProxy(0); delete d->fileProgressProxy; d->fileProgressProxy = 0; } } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); d->image->initialRefreshGraph(); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } bool KisDocument::prepareLocksForSaving() { KisImageSP copiedImage; // XXX: Restore this when // a) cloning works correctly and // b) doesn't take ages because it needs to refresh its entire graph and finally, // c) we do use the saving image to save in the background. { Private::SafeSavingLocker locker(d, this); if (locker.successfullyLocked()) { copiedImage = d->image; //->clone(true); } else if (!isAutosaving()) { // even though it is a recovery operation, we should ensure we do not enter saving twice! std::unique_lock> l(d->savingLock, std::try_to_lock); if (l.owns_lock()) { d->lastErrorMessage = i18n("The image was still busy while saving. Your saved image might be incomplete."); d->image->lock(); copiedImage = d->image; //->clone(true); //copiedImage->initialRefreshGraph(); d->image->unlock(); } } } bool result = false; // ensure we do not enter saving twice if (copiedImage && d->savingMutex.tryLock()) { d->savingImage = copiedImage; result = true; } else { qWarning() << "Could not lock the document for saving!"; d->lastErrorMessage = i18n("Could not lock the image for saving."); } return result; } void KisDocument::unlockAfterSaving() { d->savingImage = 0; d->savingMutex.unlock(); } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index f882411b74..405e935ef4 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,629 +1,627 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); } void remove(KoShape *child) override { SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvas * canvas; KoShapeBasedDocumentBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(controller); Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { addShape(shape->cloneShape()); } } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); m_d->canvas = new KisShapeLayerCanvas(this, image()); m_d->canvas->setProjection(m_d->paintDevice); m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(const QPointF&)), SLOT(slotMoveShapes(const QPointF&))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { KisExternalLayer::setVisible(visible, isLoading); } void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } #include "SvgWriter.h" #include "SvgParser.h" #include bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes, sizeInPt); writer.save(storeDev); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(false); QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(device, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->paintDevice->clear(); QList shapes = m_d->canvas->shapeManager()->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } diff --git a/libs/ui/kis_animation_cache_populator.cpp b/libs/ui/kis_animation_cache_populator.cpp index 5e83991a0b..952ce7231f 100644 --- a/libs/ui/kis_animation_cache_populator.cpp +++ b/libs/ui/kis_animation_cache_populator.cpp @@ -1,312 +1,309 @@ /* * 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_cache_populator.h" #include #include #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_canvas2.h" #include "kis_time_range.h" #include "kis_animation_frame_cache.h" #include "kis_update_info.h" #include "kis_signal_auto_connection.h" #include "kis_idle_watcher.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_keyframe_channel.h" #include "KisAnimationCacheRegenerator.h" struct KisAnimationCachePopulator::Private { KisAnimationCachePopulator *q; KisPart *part; QTimer timer; /** * Counts up the number of subsequent times Krita has been detected idle. */ int idleCounter; static const int IDLE_COUNT_THRESHOLD = 4; static const int IDLE_CHECK_INTERVAL = 500; static const int BETWEEN_FRAMES_INTERVAL = 10; int requestedFrame; KisAnimationFrameCacheSP requestCache; KisOpenGLUpdateInfoSP requestInfo; KisSignalAutoConnectionsStore imageRequestConnections; QFutureWatcher infoConversionWatcher; KisAnimationCacheRegenerator regenerator; bool calculateAnimationCacheInBackground = true; enum State { NotWaitingForAnything, WaitingForIdle, WaitingForFrame, BetweenFrames }; State state; Private(KisAnimationCachePopulator *_q, KisPart *_part) : q(_q), part(_part), idleCounter(0), requestedFrame(-1), state(WaitingForIdle) { timer.setSingleShot(true); } void timerTimeout() { switch (state) { case WaitingForIdle: case BetweenFrames: generateIfIdle(); break; case WaitingForFrame: KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug"); break; case NotWaitingForAnything: KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug"); break; } } void generateIfIdle() { if (part->idleWatcher()->isIdle()) { idleCounter++; if (idleCounter >= IDLE_COUNT_THRESHOLD) { if (!tryRequestGeneration()) { enterState(NotWaitingForAnything); } return; } } else { idleCounter = 0; } enterState(WaitingForIdle); } bool tryRequestGeneration() { // Prioritize the active document KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0); KisMainWindow *activeWindow = part->currentMainwindow(); if (activeWindow && activeWindow->activeView()) { KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase(); if (activeCanvas && activeCanvas->frameCache()) { activeDocumentCache = activeCanvas->frameCache(); // Let's skip frames affected by changes to the active node (on the active document) // This avoids constant invalidation and regeneration while drawing KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode(); KisTimeRange skipRange; if (activeNode) { int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime(); - const QList channels = - activeNode->keyframeChannels(); - - if (!channels.isEmpty()) { - Q_FOREACH (const KisKeyframeChannel *channel, channels) { + if (!activeNode->keyframeChannels().isEmpty()) { + Q_FOREACH (const KisKeyframeChannel *channel, activeNode->keyframeChannels()) { skipRange |= channel->affectedFrames(currentTime); } } else { skipRange = KisTimeRange::infinite(0); } } bool requested = tryRequestGeneration(activeDocumentCache, skipRange); if (requested) return true; } } QList caches = KisAnimationFrameCache::caches(); KisAnimationFrameCache *cache; Q_FOREACH (cache, caches) { if (cache == activeDocumentCache.data()) { // We already handled this one... continue; } bool requested = tryRequestGeneration(cache, KisTimeRange()); if (requested) return true; } return false; } bool tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeRange skipRange) { KisImageSP image = cache->image(); if (!image) return false; KisImageAnimationInterface *animation = image->animationInterface(); KisTimeRange currentRange = animation->fullClipRange(); const int frame = KisAnimationCacheRegenerator::calcFirstDirtyFrame(cache, currentRange, skipRange); if (frame >= 0) { return regenerate(cache, frame); } return false; } bool regenerate(KisAnimationFrameCacheSP cache, int frame) { if (state == WaitingForFrame) { // Already busy, deny request return false; } /** * We should enter the state before the frame is * requested. Otherwise the signal may come earlier than we * enter it. */ enterState(WaitingForFrame); regenerator.startFrameRegeneration(frame, cache); return true; } QString debugStateToString(State newState) { QString str = ""; switch (newState) { case WaitingForIdle: str = "WaitingForIdle"; break; case WaitingForFrame: str = "WaitingForFrame"; break; case NotWaitingForAnything: str = "NotWaitingForAnything"; break; case BetweenFrames: str = "BetweenFrames"; break; } return str; } void enterState(State newState) { state = newState; int timerTimeout = -1; switch (state) { case WaitingForIdle: timerTimeout = IDLE_CHECK_INTERVAL; break; case WaitingForFrame: // the timeout is handled by the regenerator now timerTimeout = -1; break; case NotWaitingForAnything: // frame conversion cannot be cancelled, // so there is no timeout timerTimeout = -1; break; case BetweenFrames: timerTimeout = BETWEEN_FRAMES_INTERVAL; break; } if (timerTimeout >= 0) { timer.start(timerTimeout); } else { timer.stop(); } } }; KisAnimationCachePopulator::KisAnimationCachePopulator(KisPart *part) : m_d(new Private(this, part)) { connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer())); connect(&m_d->regenerator, SIGNAL(sigFrameCancelled()), SLOT(slotRegeneratorFrameCancelled())); connect(&m_d->regenerator, SIGNAL(sigFrameFinished()), SLOT(slotRegeneratorFrameReady())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); } KisAnimationCachePopulator::~KisAnimationCachePopulator() {} bool KisAnimationCachePopulator::regenerate(KisAnimationFrameCacheSP cache, int frame) { return m_d->regenerate(cache, frame); } void KisAnimationCachePopulator::slotTimer() { m_d->timerTimeout(); } void KisAnimationCachePopulator::slotRequestRegeneration() { // skip if the user forbade background regeneration if (!m_d->calculateAnimationCacheInBackground) return; m_d->enterState(Private::WaitingForIdle); } void KisAnimationCachePopulator::slotRegeneratorFrameCancelled() { KIS_ASSERT_RECOVER_RETURN(m_d->state == Private::WaitingForFrame); m_d->enterState(Private::NotWaitingForAnything); } void KisAnimationCachePopulator::slotRegeneratorFrameReady() { m_d->enterState(Private::BetweenFrames); } void KisAnimationCachePopulator::slotConfigChanged() { KisConfig cfg; m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground(); } diff --git a/libs/ui/kis_favorite_resource_manager.cpp b/libs/ui/kis_favorite_resource_manager.cpp index 1b8c1d03f2..eb148f6e17 100644 --- a/libs/ui/kis_favorite_resource_manager.cpp +++ b/libs/ui/kis_favorite_resource_manager.cpp @@ -1,369 +1,369 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "kis_favorite_resource_manager.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "KisViewManager.h" #include "kis_resource_server_provider.h" #include "kis_min_heap.h" #include "kis_config.h" #include "kis_config_notifier.h" class KisFavoriteResourceManager::ColorDataList { public: static const int MAX_RECENT_COLOR = 12; ColorDataList() { m_key = 0; } ~ColorDataList() { qDeleteAll(m_guiList); } int size() { return m_guiList.size(); } int leastUsedGuiPos() { return findPos(m_priorityList.valueAt(0)); } const KoColor& guiColor(int pos) { Q_ASSERT_X(pos < size(), "ColorDataList::guiColor", "index out of bound"); Q_ASSERT_X(pos >= 0, "ColorDataList::guiColor", "negative index"); return m_guiList.at(pos)->data; } void append(const KoColor& data) { int pos = findPos(data); if (pos > -1) updateKey(pos); else appendNew(data); } void appendNew(const KoColor& data) { if (size() >= ColorDataList::MAX_RECENT_COLOR) removeLeastUsed(); PriorityNode * node; node = new PriorityNode (); node->data = data; node->key = m_key++; m_priorityList.append(node); int pos = guiInsertPos(data); pos >= m_guiList.size() ? m_guiList.append(node) : m_guiList.insert(pos, node); node = 0; } void removeLeastUsed() { Q_ASSERT_X(size() >= 0, "ColorDataList::removeLeastUsed", "index out of bound"); if (size() <= 0) return; int pos = findPos(m_priorityList.valueAt(0)); m_guiList.removeAt(pos); m_priorityList.remove(0); } void updateKey(int guiPos) { if (m_guiList.at(guiPos)->key == m_key - 1) return; m_priorityList.changeKey(m_guiList.at(guiPos)->pos, m_key++); } /*find position of the color on the gui list*/ int findPos(const KoColor& color) { int low = 0, high = size(), mid = 0; while (low < high) { mid = (low + high) / 2; if (hsvComparison(color, m_guiList.at(mid)->data) == 0) return mid; else if (hsvComparison(color, m_guiList.at(mid)->data) < 0) high = mid; else low = mid + 1; } return -1; } private: int m_key; int guiInsertPos(const KoColor& color) { int low = 0, high = size() - 1, mid = (low + high) / 2; while (low < high) { hsvComparison(color, m_guiList[mid]->data) == -1 ? high = mid : low = mid + 1; mid = (low + high) / 2; } if (m_guiList.size() > 0) { if (hsvComparison(color, m_guiList[mid]->data) == 1) ++mid; } return mid; } /*compares c1 and c2 based on HSV. c1 < c2, returns -1 c1 = c2, returns 0 c1 > c2, returns 1 */ int hsvComparison(const KoColor& c1, const KoColor& c2) { QColor qc1 = c1.toQColor(); QColor qc2 = c2.toQColor(); if (qc1.hue() < qc2.hue()) return -1; if (qc1.hue() > qc2.hue()) return 1; // hue is the same, ok let's compare saturation if (qc1.saturation() < qc2.saturation()) return -1; if (qc1.saturation() > qc2.saturation()) return 1; // oh, also saturation is same? if (qc1.value() < qc2.value()) return -1; if (qc1.value() > qc2.value()) return 1; // user selected two similar colors return 0; } KisMinHeap m_priorityList; QList *> m_guiList; }; KisFavoriteResourceManager::KisFavoriteResourceManager(KisPaintopBox *paintopBox) : m_paintopBox(paintopBox) , m_colorList(0) , m_blockUpdates(false) , m_initialized(false) { KisConfig cfg; m_maxPresets = cfg.favoritePresets(); m_colorList = new ColorDataList(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(configChanged())); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(false); rServer->addObserver(this); } KisFavoriteResourceManager::~KisFavoriteResourceManager() { KisConfig cfg; cfg.writeEntry("favoritePresetsTag", m_currentTag); KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->removeObserver(this); delete m_colorList; } void KisFavoriteResourceManager::unsetResourceServer() { // ... } QVector KisFavoriteResourceManager::favoritePresetList() { init(); return m_favoritePresetsList; } QList KisFavoriteResourceManager::favoritePresetImages() { init(); QList images; Q_FOREACH (KisPaintOpPresetSP preset, m_favoritePresetsList) { if (preset) { images.append(preset->image()); } } return images; } void KisFavoriteResourceManager::setCurrentTag(const QString& tagName) { m_currentTag = tagName; updateFavoritePresets(); } void KisFavoriteResourceManager::slotChangeActivePaintop(int pos) { if (pos < 0 || pos >= m_favoritePresetsList.size()) return; KoResource* resource = const_cast(m_favoritePresetsList.at(pos).data()); m_paintopBox->resourceSelected(resource); emit hidePalettes(); } int KisFavoriteResourceManager::numFavoritePresets() { init(); return m_favoritePresetsList.size(); } //Recent Colors void KisFavoriteResourceManager::slotUpdateRecentColor(int pos) { // Do not update the key, the colour might be selected but it is not used yet. So we are not supposed // to update the colour priority when we select it. m_colorList->updateKey(pos); emit setSelectedColor(pos); emit sigSetFGColor(m_colorList->guiColor(pos)); emit hidePalettes(); } void KisFavoriteResourceManager::slotAddRecentColor(const KoColor& color) { m_colorList->append(color); int pos = m_colorList->findPos(color); emit setSelectedColor(pos); } void KisFavoriteResourceManager::slotChangeFGColorSelector(KoColor c) { emit sigChangeFGColorSelector(c); } void KisFavoriteResourceManager::removingResource(PointerType resource) { if (m_blockUpdates) { return; } if (m_favoritePresetsList.contains(resource.data())) { updateFavoritePresets(); } } void KisFavoriteResourceManager::resourceAdded(PointerType /*resource*/) { if (m_blockUpdates) { return; } updateFavoritePresets(); } void KisFavoriteResourceManager::resourceChanged(PointerType /*resource*/) { } void KisFavoriteResourceManager::setBlockUpdates(bool block) { m_blockUpdates = block; if (!block) { updateFavoritePresets(); } } void KisFavoriteResourceManager::syncTaggedResourceView() { if (m_blockUpdates) { return; } updateFavoritePresets(); } void KisFavoriteResourceManager::syncTagAddition(const QString& /*tag*/) {} void KisFavoriteResourceManager::syncTagRemoval(const QString& /*tag*/) {} int KisFavoriteResourceManager::recentColorsTotal() { return m_colorList->size(); } const KoColor& KisFavoriteResourceManager::recentColorAt(int pos) { return m_colorList->guiColor(pos); } void KisFavoriteResourceManager::slotSetBGColor(const KoColor c) { m_bgColor = c; } KoColor KisFavoriteResourceManager::bgColor() const { return m_bgColor; } bool sortPresetByName(KisPaintOpPresetSP preset1, KisPaintOpPresetSP preset2) { return preset1->name() < preset2->name(); } void KisFavoriteResourceManager::updateFavoritePresets() { m_favoritePresetsList.clear(); KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(false); QStringList presetFilenames = rServer->searchTag(m_currentTag); for(int i = 0; i < qMin(m_maxPresets, presetFilenames.size()); i++) { KisPaintOpPresetSP pr = rServer->resourceByFilename(presetFilenames.at(i)); m_favoritePresetsList.append(pr.data()); qSort(m_favoritePresetsList.begin(), m_favoritePresetsList.end(), sortPresetByName); } emit updatePalettes(); } void KisFavoriteResourceManager::configChanged() { KisConfig cfg; m_maxPresets = cfg.favoritePresets(); updateFavoritePresets(); } void KisFavoriteResourceManager::init() { if (!m_initialized) { m_initialized = true; KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(true); KConfigGroup group( KSharedConfig::openConfig(), "favoriteList"); QStringList oldFavoritePresets = (group.readEntry("favoritePresets")).split(',', QString::SkipEmptyParts); KisConfig cfg; m_currentTag = cfg.readEntry("favoritePresetsTag", "Block"); if (!oldFavoritePresets.isEmpty() && m_currentTag.isEmpty()) { - m_currentTag = i18n("Favorite Presets"); + m_currentTag = i18n("Number of popup palette presets shown"); Q_FOREACH ( const QString& name, oldFavoritePresets) { KisPaintOpPresetSP preset = rServer->resourceByName(name); rServer->addTag(preset.data(), m_currentTag); } rServer->tagCategoryAdded(m_currentTag); cfg.writeEntry("favoritePresets", QString()); } updateFavoritePresets(); } } diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 5e3c69954c..7981cec326 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1324 +1,1324 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_paintop_box.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 "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "widgets/kis_popup_button.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "kis_highlighted_button.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->resourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg; m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisPopupButton(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // pen pressure m_disablePressureButton = new KisHighlightedToolButton(this); m_disablePressureButton->setFixedSize(iconsize, iconsize); m_disablePressureButton->setCheckable(true); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); m_disablePressureButton->setDefaultAction(m_disablePressureAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); KisAction* hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); hideCanvasDecorationsX->setCheckable(true); hideCanvasDecorationsX->setText(i18n("Hide Mirror Line")); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); KisAction* lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); lockActionX->setText(i18n("Lock")); lockActionX->setCheckable(true); toolbarMenuXMirror->addAction(lockActionX); KisAction* moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); moveToCenterActionX->setCheckable(false); moveToCenterActionX->setText(i18n("Move to Canvas Center")); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); KisAction* hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); hideCanvasDecorationsY->setCheckable(true); hideCanvasDecorationsY->setText(i18n("Hide Mirror Line")); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); KisAction* lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); lockActionY->setText(i18n("Lock")); lockActionY->setCheckable(true); toolbarMenuYMirror->addAction(lockActionY); KisAction* moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); moveToCenterActionY->setCheckable(false); moveToCenterActionY->setText(i18n("Move to Canvas Center")); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0.0, 1.0, 2); slOpacity->setValue(1.0); slOpacity->setSingleStep(0.05); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0.0, 1.0, 2); slFlow->setValue(1.0); slFlow->setSingleStep(0.05); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); QWidget* compositePressure = new QWidget(this); QHBoxLayout* pressureLayout = new QHBoxLayout(compositePressure); pressureLayout->addWidget(m_disablePressureButton); pressureLayout->setSpacing(4); pressureLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); view->actionCollection()->addAction("pressure_action", action); action->setText(i18n("Pressure usage (small button)")); action->setDefaultWidget(compositePressure); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("workspaces", action); view->actionCollection()->addAction("workspaces", action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(savePresetClicked()) , SLOT(slotSaveActivePreset())); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(const KisNodeSP)) , SLOT(slotNodeChanged(const KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); m_favoriteResourceManager = new KisFavoriteResourceManager(this); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg; QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); //qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } } // Do not delete the widget, since it it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); //qDebug() << "restoreResource" << resource << preset; if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset && preset != m_resourceProvider->currentPreset()) { if (!preset->settings()->isLoadable()) return; if (!m_dirtyPresetsEnabled) { KisSignalsBlocker blocker(m_optionWidget); if (!preset->load()) { warnKrita << "failed to load the preset."; } } //qDebug() << "resourceSelected" << resource->name(); setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); //qDebug() << "setCurrentPaintop();" << paintop << preset; setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { //qDebug() << "setCurrentPaintop(); " << preset->name(); if (preset == m_resourceProvider->currentPreset()) { if (preset == m_tabletToolMap[m_currTabletToolID].preset) { return; } } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id()); QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap()); m_brushEditorPopupButton->setIcon(QIcon(pixFilename)); m_presetsPopup->setCurrentPaintOpId(paintop.id()); ////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name(); m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset; m_tabletToolMap[m_currTabletToolID].preset = preset; m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp(); if (m_presetsPopup->currentPaintOpId() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset.data()); m_presetsPopup->updateViewSettings(); // the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed // need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation m_optionWidget->setImage(m_viewManager->image()); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset = new KisPaintOpPreset(path); if (!preset->load()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } if (compositeOpID == COMPOSITE_ERASE) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); slider->setValue(value); } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); //qDebug() << "slotsetpaintop" << id; setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); //qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId(); m_currTabletToolID = TabletToolID(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg; KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), "Eraser_circle")); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), "Basic_tip_default")); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset fcound for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (preset) { //qDebug() << "inputdevicechanged 1" << preset; setCurrentPaintop(preset); } } else { if (toolData->preset) { //qDebug() << "inputdevicechanged 2" << toolData->preset; setCurrentPaintop(toolData->preset); } else { //qDebug() << "inputdevicechanged 3" << toolData->paintOpID; setCurrentPaintop(toolData->paintOpID); } } } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset.data()); } /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotSaveActivePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); if (!curPreset) return; m_favoriteResourceManager->setBlockUpdates(true); KisPaintOpPresetSP newPreset = curPreset->clone(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); QString presetName = m_presetsPopup->getPresetName(); QString presetFilename = saveLocation + presetName + newPreset->defaultFileExtension(); QStringList tags; KisPaintOpPresetSP resource = rServer->resourceByName(presetName); if (resource) { tags = rServer->assignedTagsList(resource.data()); rServer->removeResourceAndBlacklist(resource); } newPreset->setImage(m_presetsPopup->cutOutOverlay()); newPreset->setFilename(presetFilename); newPreset->setName(presetName); newPreset->setPresetDirty(false); rServer->addResource(newPreset); Q_FOREACH (const QString & tag, tags) { rServer->addTag(newPreset.data(), tag); } // HACK ALERT! the server does not notify the observers // automatically, so we need to call theupdate manually! rServer->tagCategoryMembersChanged(); restoreResource(newPreset.data()); m_favoriteResourceManager->setBlockUpdates(false); } void KisPaintopBox::slotUpdatePreset() { if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value(); qreal flow = m_sliderChooser[n]->getWidget("flow")->value(); qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it wont work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { - setWidgetState(ENABLE_PRESETS | ENABLE_SIZE | ENABLE_FLOW); + setWidgetState(ENABLE_PRESETS); slotUpdatePreset(); m_presetsEnabled = true; } else { - setWidgetState(DISABLE_PRESETS | DISABLE_SIZE | DISABLE_FLOW); + setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == preset->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } return; } i++; } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset()->name() == preset->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } return; } i++; } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); //Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until thye entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset.data()); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data()); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } //slotUpdatePreset(); } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg; cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg; cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg; cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg; if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc index d843c15b94..47b93df966 100644 --- a/libs/ui/tool/kis_tool_freehand.cc +++ b/libs/ui/tool/kis_tool_freehand.cc @@ -1,457 +1,458 @@ /* * kis_tool_freehand.cc - part of Krita * * Copyright (c) 2003-2007 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * Copyright (c) 2007,2008,2010 Cyrille Berger * Copyright (c) 2009 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_tool_freehand.h" #include #include #include #include #include #include #include #include #include #include //pop up palette #include // Krita/image #include #include #include #include #include #include // Krita/ui #include "kis_abstract_perspective_grid.h" #include "kis_config.h" #include "canvas/kis_canvas2.h" #include "kis_cursor.h" #include #include #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "kis_recording_adapter.h" #include "strokes/freehand_stroke.h" using namespace std::placeholders; // For _1 placeholder KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText) : KisToolPaint(canvas, cursor), m_paintopBasedPickingInAction(false), m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1)) { m_assistant = false; m_magnetism = 1.0; m_only_one_assistant = true; setSupportOutline(true); setMaskSyntheticEvents(true); // Disallow mouse events from finger presses. m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this); m_recordingAdapter = new KisRecordingAdapter(); m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText, m_recordingAdapter); connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline())); } KisToolFreehand::~KisToolFreehand() { delete m_helper; delete m_recordingAdapter; delete m_infoBuilder; } KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const { return m_helper->smoothingOptions(); } void KisToolFreehand::resetCursorStyle() { KisConfig cfg; switch (cfg.newCursorStyle()) { case CURSOR_STYLE_NO_CURSOR: useCursor(KisCursor::blankCursor()); break; case CURSOR_STYLE_POINTER: useCursor(KisCursor::arrowCursor()); break; case CURSOR_STYLE_SMALL_ROUND: useCursor(KisCursor::roundCursor()); break; case CURSOR_STYLE_CROSSHAIR: useCursor(KisCursor::crossCursor()); break; case CURSOR_STYLE_TRIANGLE_RIGHTHANDED: useCursor(KisCursor::triangleRightHandedCursor()); break; case CURSOR_STYLE_TRIANGLE_LEFTHANDED: useCursor(KisCursor::triangleLeftHandedCursor()); break; case CURSOR_STYLE_BLACK_PIXEL: useCursor(KisCursor::pixelBlackCursor()); break; case CURSOR_STYLE_WHITE_PIXEL: useCursor(KisCursor::pixelWhiteCursor()); break; case CURSOR_STYLE_TOOLICON: default: KisToolPaint::resetCursorStyle(); break; } } KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const { return m_infoBuilder; } KisRecordingAdapter* KisToolFreehand::recordingAdapter() const { return m_recordingAdapter; } void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper) { delete m_helper; m_helper = helper; } int KisToolFreehand::flags() const { - return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET; + return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET + |KisTool::FLAG_USES_CUSTOM_SIZE; } void KisToolFreehand::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolFreehand::deactivate() { if (mode() == PAINT_MODE) { endStroke(); setMode(KisTool::HOVER_MODE); } KisToolPaint::deactivate(); } void KisToolFreehand::initStroke(KoPointerEvent *event) { m_helper->initPaint(event, canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolFreehand::doStroke(KoPointerEvent *event) { //set canvas information here?// KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { m_helper->setCanvasHorizontalMirrorState(canvas2->xAxisMirrored()); m_helper->setCanvasRotation(canvas2->rotationAngle()); } m_helper->paint(event); } void KisToolFreehand::endStroke() { m_helper->endPaint(); } bool KisToolFreehand::primaryActionSupportsHiResEvents() const { return true; } void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event) { // FIXME: workaround for the Duplicate Op tryPickByPaintOp(event, PickFgImage); requestUpdateOutline(event->point, event); NodePaintAbility paintability = nodePaintAbility(); if (!nodeEditable() || paintability != PAINT) { if(paintability == KisToolPaint::VECTOR){ KisCanvas2 * kiscanvas = static_cast(canvas()); QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask."); kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); } event->ignore(); return; } setMode(KisTool::PAINT_MODE); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->disableControls(); } initStroke(event); } void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); requestUpdateOutline(event->point, event); /** * Actual painting */ doStroke(event); } void KisToolFreehand::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); endStroke(); if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->endStroke(); } notifyModified(); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->enableControls(); } setMode(KisTool::HOVER_MODE); } bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action) { if (action != PickFgNode && action != PickFgImage) return false; /** * FIXME: we need some better way to implement modifiers * for a paintop level. This method is used in DuplicateOp only! */ QPointF pos = adjustPosition(event->point, event->point); qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->resourceProvider()->perspectiveGrids()) { if (grid && grid->contains(pos)) { perspective = grid->distance(pos); break; } } if (!currentPaintOpPreset()) { return false; } bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()-> mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point), pressureToCurve(event->pressure()), event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, 0, 0), event->modifiers(), currentNode()); return !paintOpIgnoredEvent; } void KisToolFreehand::activateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::activateAlternateAction(action); return; } useCursor(KisCursor::blankCursor()); setOutlineEnabled(true); } void KisToolFreehand::deactivateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::deactivateAlternateAction(action); return; } resetCursorStyle(); setOutlineEnabled(false); } void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action)) { m_paintopBasedPickingInAction = true; return; } if (action != ChangeSize) { KisToolPaint::beginAlternateAction(event, action); return; } setMode(GESTURE_MODE); m_initialGestureDocPoint = event->point; m_initialGestureGlobalPoint = QCursor::pos(); m_lastDocumentPoint = event->point; m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize(); } void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return; if (action != ChangeSize) { KisToolPaint::continueAlternateAction(event, action); return; } QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint); QPointF actualWidgetPosition = convertDocumentToWidget(event->point); QPointF offset = actualWidgetPosition - lastWidgetPosition; KisCanvas2 *canvas2 = dynamic_cast(canvas()); QRect screenRect = QApplication::desktop()->screenGeometry(); qreal scaleX = 0; qreal scaleY = 0; canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY); const qreal maxBrushSize = KisConfig().readEntry("maximumBrushSize", 1000); const qreal effectiveMaxDragSize = 0.5 * screenRect.width(); const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX); const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize; const qreal sizeDiff = scaleCoeff * offset.x() ; if (qAbs(sizeDiff) > 0.01) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); //m_brushResizeCompressor.start(newSize); m_lastDocumentPoint = event->point; m_lastPaintOpSize = newSize; } } void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) { m_paintopBasedPickingInAction = false; return; } if (action != ChangeSize) { KisToolPaint::endAlternateAction(event, action); return; } QCursor::setPos(m_initialGestureGlobalPoint); requestUpdateOutline(m_initialGestureDocPoint, 0); setMode(HOVER_MODE); } bool KisToolFreehand::wantsAutoScroll() const { return false; } void KisToolFreehand::setAssistant(bool assistant) { m_assistant = assistant; } void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant) { m_only_one_assistant = assistant; } void KisToolFreehand::slotDoResizeBrush(qreal newSize) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); } QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant); QPointF ap = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin); return (1.0 - m_magnetism) * point + m_magnetism * ap; } return point; } qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint) { qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->resourceProvider()->perspectiveGrids()) { if (grid && grid->contains(documentPoint)) { perspective = grid->distance(documentPoint); break; } } return perspective; } void KisToolFreehand::explicitUpdateOutline() { requestUpdateOutline(m_outlineDocPoint, 0); } QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { QPointF imagePos = currentImage()->documentToPixel(documentPos); if (currentPaintOpPreset()) return m_helper->paintOpOutline(imagePos, event, currentPaintOpPreset()->settings(), outlineMode); else return QPainterPath(); } diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index 97e6ef1c2a..306a84a914 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,254 +1,255 @@ /* * 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_tool_shape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { - return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET; + return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET + |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); } KisPainter::FillStyle KisToolShape::fillStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { return KisPainter::FillStyleNone; } } KisPainter::StrokeStyle KisToolShape::strokeStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { return KisPainter::StrokeStyleNone; } } qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } void KisToolShape::setupPaintAction(KisRecordedPaintAction* action) { KisToolPaint::setupPaintAction(action); action->setFillStyle(fillStyle()); action->setStrokeStyle(strokeStyle()); action->setGenerator(currentGenerator()); action->setPattern(currentPattern()); action->setGradient(currentGradient()); } void KisToolShape::addShape(KoShape* shape) { KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { case KisPainter::FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; case KisPainter::FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; case KisPainter::FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; case KisPainter::FillStyleGradient: { QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1)); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setStops(currentGradient()->toQGradient()->stops()); QSharedPointer gradientFill(new KoGradientBackground(gradient)); shape->setBackground(gradientFill); } break; case KisPainter::FillStyleNone: default: shape->setBackground(QSharedPointer(0)); break; } KUndo2Command * cmd = canvas()->shapeController()->addShape(shape); canvas()->addCommand(cmd); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); if (!node || !blockUntilOperationsFinished()) { return; } // Get painting options KisPaintOpPresetSP preset = currentPaintOpPreset(); // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); // Recorde the paint action KisRecordedPathPaintAction bezierCurvePaintAction( KisNodeQueryPath::absolutePath(node), preset ); bezierCurvePaintAction.setPaintColor(currentFgColor()); QPointF lastPoint, nextPoint; int elementCount = mapedOutline.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = mapedOutline.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: if (i != 0) { qFatal("Unhandled"); // XXX: I am not sure the tool can produce such element, deal with it when it can } lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); bezierCurvePaintAction.addLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(mapedOutline.elementAt(i + 2).x, mapedOutline.elementAt(i + 2).y); bezierCurvePaintAction.addCurve(KisPaintInformation(lastPoint), QPointF(mapedOutline.elementAt(i).x, mapedOutline.elementAt(i).y), QPointF(mapedOutline.elementAt(i + 1).x, mapedOutline.elementAt(i + 1).y), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; default: continue; } } image->actionRecorder()->addAction(bezierCurvePaintAction); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } notifyModified(); } diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp index f2d8794e18..f207c8c6a0 100644 --- a/libs/ui/widgets/kis_slider_spin_box.cpp +++ b/libs/ui/widgets/kis_slider_spin_box.cpp @@ -1,1037 +1,1038 @@ /* This file is part of the KDE project * Copyright (c) 2010 Justin Noel * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2015 Moritz Molch * * 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_slider_spin_box.h" #include #include #include #include #include #include #include #include #include #include #include - +#include "kis_cursor.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include "kis_num_parser.h" class KisAbstractSliderSpinBoxPrivate { public: enum Style { STYLE_NOQUIRK, STYLE_PLASTIQUE, STYLE_BREEZE, STYLE_FUSION, }; QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; qreal slowFactor; qreal shiftPercent; bool shiftMode; QString prefix; QString suffix; qreal exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; Style style; bool blockUpdateSignalOnDrag; bool isDragging; bool parseInt; }; KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d) : QWidget(parent) , d_ptr(_d) { Q_D(KisAbstractSliderSpinBox); QEvent e(QEvent::StyleChange); changeEvent(&e); d->upButtonDown = false; d->downButtonDown = false; d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->setContentsMargins(0,0,0,0); d->edit->installEventFilter(this); //Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->value = 0; d->minimum = 0; d->maximum = 100; d->factor = 1.0; d->singleStep = 1; d->fastSliderStep = 5; d->slowFactor = 0.1; d->shiftMode = false; d->blockUpdateSignalOnDrag = false; d->isDragging = false; d->parseInt = false; setExponentRatio(1.0); + setCursor(KisCursor::splitHCursor()); //Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); //dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox() { Q_D(KisAbstractSliderSpinBox); delete d; } void KisAbstractSliderSpinBox::showEdit() { Q_D(KisAbstractSliderSpinBox); if (d->edit->isVisible()) return; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) { d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0)); } else { d->edit->setGeometry(progressRect(spinBoxOptions())); } d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); KisInputManager *inputManager = KisPart::instance()->currentInputManager(); if (inputManager) { inputManager->slotFocusOnEnter(false); } } void KisAbstractSliderSpinBox::hideEdit() { Q_D(KisAbstractSliderSpinBox); d->edit->hide(); update(); KisInputManager *inputManager = KisPart::instance()->currentInputManager(); if (inputManager) { inputManager->slotFocusOnEnter(true); } } void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(KisAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: paintFusion(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: paintPlastique(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: paintBreeze(painter); break; default: paint(painter); break; } painter.end(); } void KisAbstractSliderSpinBox::paint(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); //Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.rect.adjust(0, 2, 0, -2); //Draw "SpinBox".Clip off the area of the lineEdit to avoid double //borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(progressRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); QStyleOptionProgressBar progressOpts = progressBarOptions(); progressOpts.rect.adjust(0, 2, 0, -2); style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); //Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void KisAbstractSliderSpinBox::paintFusion(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.frame = true; spinOpts.rect.adjust(0, -1, 0, 1); //spinOpts.palette().setBrush(QPalette::Base, palette().highlight()); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(1,2,-4,-2); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setClipRect(leftRect.adjusted(0, -1, 1, 1)); painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); spinOpts.palette.setBrush(QPalette::Base, palette().highlight()); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); } painter.setClipping(false); } painter.restore(); } void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(2,0,-2,0); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); painter.drawRect(leftRect.adjusted(0,0,0,-1)); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect.adjusted(0,0,1,0)); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } } painter.restore(); } void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); QString valueText = progressOpts.text; progressOpts.text = ""; progressOpts.rect.adjust(0, 1, 0, -1); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this); style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this); painter.save(); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) { leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height()); } else if (progressIndicatorPos > progressOpts.rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = progressOpts.rect; rightRect = rightRect.subtracted(leftRect); painter.setClipRegion(rightRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } if (!leftRect.isNull()) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect); style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } } painter.restore(); } void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Depress buttons or highlight slider //Also used to emulate mouse grab... if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } update(); } void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); d->isDragging = false; //Step up/down for buttons //Emualting mouse grab too if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (progressRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { //Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } else { // Confirm the last known value, since we might be ignoring move events setInternalValue(d->value); } d->upButtonDown = false; d->downButtonDown = false; update(); } void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; } } else { d->shiftMode = false; } //Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { d->isDragging = true; setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag); update(); } } void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(KisAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: //Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(KisAbstractSliderSpinBox); if ( e->delta() > 0) { setInternalValue(d->value + d->singleStep); } else { setInternalValue(d->value - d->singleStep); } update(); e->accept(); } void KisAbstractSliderSpinBox::commitEnteredValue() { Q_D(KisAbstractSliderSpinBox); //QLocale locale; bool ok = false; //qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor; qreal value; if (d->parseInt) { value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor; } else { value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor; } if (ok) { setInternalValue(value); } } bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(KisAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: { commitEnteredValue(); hideEdit(); return true; } case Qt::Key_Escape: d->edit->setText(valueString()); hideEdit(); return true; default: break; } } return false; } QSize KisAbstractSliderSpinBox::sizeHint() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFont ft(font()); if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) { // Some styles use bold font in progressbars // unfortunately there is no reliable way to check for that ft.setBold(true); } QFontMetrics fm(ft); QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size()); hint += QSize(0, 2); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: hint += QSize(8, 8); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: hint += QSize(8, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: hint += QSize(2, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK: // almost all "modern" styles have a margin around controls hint += QSize(6, 6); break; default: break; } //Getting the size of the buttons is a pain as the calcs require a rect //that is "big enough". We run the calc twice to get the "smallest" buttons //This code was inspired by QAbstractSpinBox QSize extra(1000, 0); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect.setSize(hint); return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint) .expandedTo(QApplication::globalStrut()); } QSize KisAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QSize KisAbstractSliderSpinBox::minimumSize() const { return QWidget::minimumSize().expandedTo(minimumSizeHint()); } QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; //Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } //Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; qreal minDbl = d->minimum; qreal dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = d->prefix + valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); //Change opts rect to be only the ComboBox's text area progressOpts.rect = progressRect(spinOpts); return progressOpts; } QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const { const Q_D(KisAbstractSliderSpinBox); QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: ret.adjust(-2, 0, 1, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: ret.adjust(1, 0, 0, 0); break; default: break; } return ret; } QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QRect correctedProgRect; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) { correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0); } else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) { correctedProgRect = progressRect(spinOpts); } else { //Adjust for magic number in style code (margins) correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2); } //Compute the distance of the progress bar, in pixel qreal leftDbl = correctedProgRect.left(); qreal xDbl = x - leftDbl; //Compute the ration of the progress bar used, linearly (ignoring the exponent) qreal rightDbl = correctedProgRect.right(); qreal minDbl = d->minimum; qreal maxDbl = d->maximum; qreal dValues = (maxDbl - minDbl); qreal percent = (xDbl / (rightDbl - leftDbl)); //If SHIFT is pressed, movement should be slowed. if( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor; } //Final value qreal realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl); //If key CTRL is pressed, round to the closest step. if( modifiers & Qt::ControlModifier ) { qreal fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep*=d->slowFactor; } realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep; } //Return the value return int(realvalue); } void KisAbstractSliderSpinBox::setPrefix(const QString& prefix) { Q_D(KisAbstractSliderSpinBox); d->prefix = prefix; } void KisAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(KisAbstractSliderSpinBox); d->suffix = suffix; } void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl) { Q_D(KisAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->blockUpdateSignalOnDrag = blockUpdateSignal; } void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void KisAbstractSliderSpinBox::editLostFocus() { Q_D(KisAbstractSliderSpinBox); if (!d->edit->hasFocus()) { commitEnteredValue(); hideEdit(); } } void KisAbstractSliderSpinBox::setInternalValue(int value) { setInternalValue(value, false); } bool KisAbstractSliderSpinBox::isDragging() const { Q_D(const KisAbstractSliderSpinBox); return d->isDragging; } class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate) { Q_D(KisSliderSpinBox); d->parseInt = true; setRange(0,99); } KisSliderSpinBox::~KisSliderSpinBox() { } void KisSliderSpinBox::setRange(int minimum, int maximum) { Q_D(KisSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int KisSliderSpinBox::minimum() const { const Q_D(KisSliderSpinBox); return d->minimum; } void KisSliderSpinBox::setMinimum(int minimum) { Q_D(KisSliderSpinBox); setRange(minimum, d->maximum); } int KisSliderSpinBox::maximum() const { const Q_D(KisSliderSpinBox); return d->maximum; } void KisSliderSpinBox::setMaximum(int maximum) { Q_D(KisSliderSpinBox); setRange(d->minimum, maximum); } int KisSliderSpinBox::fastSliderStep() const { const Q_D(KisSliderSpinBox); return d->fastSliderStep; } void KisSliderSpinBox::setFastSliderStep(int step) { Q_D(KisSliderSpinBox); d->fastSliderStep = step; } int KisSliderSpinBox::value() { Q_D(KisSliderSpinBox); return d->value; } void KisSliderSpinBox::setValue(int value) { setInternalValue(value, false); update(); } QString KisSliderSpinBox::valueString() const { const Q_D(KisSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value, 'f', d->validator->decimals()); } void KisSliderSpinBox::setSingleStep(int value) { Q_D(KisSliderSpinBox); d->singleStep = value; } void KisSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate) { Q_D(KisDoubleSliderSpinBox); d->parseInt = false; } KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox() { } void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals) { Q_D(KisDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; //This code auto-compute a new step when pressing control. //A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } qreal KisDoubleSliderSpinBox::minimum() const { const Q_D(KisAbstractSliderSpinBox); return d->minimum / d->factor; } void KisDoubleSliderSpinBox::setMinimum(qreal minimum) { Q_D(KisAbstractSliderSpinBox); setRange(minimum, d->maximum); } qreal KisDoubleSliderSpinBox::maximum() const { const Q_D(KisAbstractSliderSpinBox); return d->maximum / d->factor; } void KisDoubleSliderSpinBox::setMaximum(qreal maximum) { Q_D(KisAbstractSliderSpinBox); setRange(d->minimum, maximum); } qreal KisDoubleSliderSpinBox::fastSliderStep() const { const Q_D(KisAbstractSliderSpinBox); return d->fastSliderStep; } void KisDoubleSliderSpinBox::setFastSliderStep(qreal step) { Q_D(KisAbstractSliderSpinBox); d->fastSliderStep = step; } qreal KisDoubleSliderSpinBox::value() { Q_D(KisAbstractSliderSpinBox); return (qreal)d->value / d->factor; } void KisDoubleSliderSpinBox::setValue(qreal value) { Q_D(KisAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor), false); update(); } void KisDoubleSliderSpinBox::setSingleStep(qreal value) { Q_D(KisAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString KisDoubleSliderSpinBox::valueString() const { const Q_D(KisAbstractSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals()); } void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } void KisAbstractSliderSpinBox::changeEvent(QEvent *e) { Q_D(KisAbstractSliderSpinBox); QWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: if (style()->objectName() == "fusion") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION; } else if (style()->objectName() == "plastique") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE; } else if (style()->objectName() == "breeze") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE; } else { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK; } break; default: break; } } diff --git a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp index f8f1d447b4..2326d806ec 100644 --- a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp +++ b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp @@ -1,429 +1,433 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kxmlguibuilder.h" #include "kxmlguiclient.h" #include "ktoolbar.h" #include "kmainwindow.h" #include "kxmlguiwindow.h" #include "kmenumenuhandler_p.h" #include #include #include #include #include #include #include #include #include #include #include #if defined(KCONFIG_BEFORE_5_24) # define authorizeAction authorizeKAction #endif using namespace KDEPrivate; class KXMLGUIBuilderPrivate { public: KXMLGUIBuilderPrivate() : m_client(0L) {} ~KXMLGUIBuilderPrivate() { } QWidget *m_widget; QString tagMainWindow; QString tagMenuBar; QString tagMenu; QString tagToolBar; QString tagStatusBar; QString tagSeparator; QString tagTearOffHandle; QString tagMenuTitle; QString attrName; QString attrLineSeparator; QString attrDomain; QString attrText1; QString attrText2; QString attrContext; QString attrIcon; KXMLGUIClient *m_client; KMenuMenuHandler *m_menumenuhandler; }; KXMLGUIBuilder::KXMLGUIBuilder(QWidget *widget) : d(new KXMLGUIBuilderPrivate) { d->m_widget = widget; d->tagMainWindow = QStringLiteral("mainwindow"); d->tagMenuBar = QStringLiteral("menubar"); d->tagMenu = QStringLiteral("menu"); d->tagToolBar = QStringLiteral("toolbar"); d->tagStatusBar = QStringLiteral("statusbar"); d->tagSeparator = QStringLiteral("separator"); d->tagTearOffHandle = QStringLiteral("tearoffhandle"); d->tagMenuTitle = QStringLiteral("title"); d->attrName = QStringLiteral("name"); d->attrLineSeparator = QStringLiteral("lineseparator"); d->attrDomain = QStringLiteral("translationDomain"); d->attrText1 = QStringLiteral("text"); d->attrText2 = QStringLiteral("Text"); d->attrContext = QStringLiteral("context"); d->attrIcon = QStringLiteral("icon"); d->m_menumenuhandler = new KMenuMenuHandler(this); } KXMLGUIBuilder::~KXMLGUIBuilder() { delete d->m_menumenuhandler; delete d; } QWidget *KXMLGUIBuilder::widget() { return d->m_widget; } QStringList KXMLGUIBuilder::containerTags() const { QStringList res; res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar; return res; } QWidget *KXMLGUIBuilder::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) { containerAction = 0; if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) { return 0; } const QString tagName = element.tagName().toLower(); if (tagName == d->tagMainWindow) { KMainWindow *mainwindow = qobject_cast(d->m_widget); // could be 0 return mainwindow; } if (tagName == d->tagMenuBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); QMenuBar *bar = 0; if (mainWin) { bar = mainWin->menuBar(); } if (!bar) { bar = new QMenuBar(d->m_widget); } bar->show(); return bar; } if (tagName == d->tagMenu) { // Look up to see if we are inside a mainwindow. If yes, then // use it as parent widget (to get kaction to plug itself into the // mainwindow). Don't use a popupmenu as parent widget, otherwise // the popup won't be hidden if it is used as a standalone menu as well. - // And we don't want to set the parent for a standalone popupmenu, - // otherwise its shortcuts appear. // // Note: menus with a parent of 0, coming from child clients, can be // leaked if the child client is deleted without a proper removeClient call, though. + QWidget *p = parent; + + if (!p && qobject_cast(d->m_widget)) { + p = d->m_widget; + } + while (p && !qobject_cast(p)) { p = p->parentWidget(); } QString name = element.attribute(d->attrName); if (!KAuthorized::authorizeAction(name)) { return 0; } QMenu *popup = new QMenu(p); popup->setObjectName(name); d->m_menumenuhandler->insertMenu(popup); QString i18nText; QDomElement textElem = element.namedItem(d->attrText1).toElement(); if (textElem.isNull()) { // try with capital T textElem = element.namedItem(d->attrText2).toElement(); } const QString text = textElem.text(); const QString context = textElem.attribute(d->attrContext); //qDebug(260) << "DOMAIN" << KLocalizedString::applicationDomain(); //qDebug(260) << "ELEMENT TEXT:" << text; if (text.isEmpty()) { // still no luck i18nText = i18n("No text"); } else { QByteArray domain = textElem.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } if (context.isEmpty()) { i18nText = i18nd(domain.constData(), text.toUtf8().constData()); } else { i18nText = i18ndc(domain.constData(), context.toUtf8().constData(), text.toUtf8().constData()); } } //qDebug(260) << "ELEMENT i18n TEXT:" << i18nText; const QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = KisIconUtils::loadIcon(icon); } if (parent) { QAction *act = popup->menuAction(); if (!icon.isEmpty()) { act->setIcon(pix); } act->setText(i18nText); if (index == -1 || index >= parent->actions().count()) { parent->addAction(act); } else { parent->insertAction(parent->actions().value(index), act); } containerAction = act; containerAction->setObjectName(name); } return popup; } if (tagName == d->tagToolBar) { QString name = element.attribute(d->attrName); KToolBar *bar = static_cast(d->m_widget->findChild(name)); if (!bar) { bar = new KToolBar(name, d->m_widget, false); } if (qobject_cast(d->m_widget)) { if (d->m_client && !d->m_client->xmlFile().isEmpty()) { bar->addXMLGUIClient(d->m_client); } } bar->loadState(element); return bar; } if (tagName == d->tagStatusBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); if (mainWin) { mainWin->statusBar()->show(); return mainWin->statusBar(); } QStatusBar *bar = new QStatusBar(d->m_widget); return bar; } return 0L; } void KXMLGUIBuilder::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) { // Warning parent can be 0L if (qobject_cast(container)) { if (parent) { parent->removeAction(containerAction); } delete container; } else if (qobject_cast(container)) { KToolBar *tb = static_cast(container); tb->saveState(element); delete tb; } else if (qobject_cast(container)) { QMenuBar *mb = static_cast(container); mb->hide(); // Don't delete menubar - it can be reused by createContainer. // If you decide that you do need to delete the menubar, make // sure that QMainWindow::d->mb does not point to a deleted // menubar object. } else if (qobject_cast(container)) { if (qobject_cast(d->m_widget)) { container->hide(); } else { delete static_cast(container); } } else { qWarning() << "Unhandled container to remove : " << container->metaObject()->className(); } } QStringList KXMLGUIBuilder::customTags() const { QStringList res; res << d->tagSeparator << d->tagTearOffHandle << d->tagMenuTitle; return res; } QAction *KXMLGUIBuilder::createCustomElement(QWidget *parent, int index, const QDomElement &element) { QAction *before = 0L; if (index > 0 && index < parent->actions().count()) { before = parent->actions().at(index); } const QString tagName = element.tagName().toLower(); if (tagName == d->tagSeparator) { if (QMenu *menu = qobject_cast(parent)) { // QMenu already cares for leading/trailing/repeated separators // no need to check anything return menu->insertSeparator(before); } else if (QMenuBar *bar = qobject_cast(parent)) { QAction *separatorAction = new QAction(bar); separatorAction->setSeparator(true); bar->insertAction(before, separatorAction); return separatorAction; } else if (KToolBar *bar = qobject_cast(parent)) { /* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator? bool isLineSep = true; QDomNamedNodeMap attributes = element.attributes(); unsigned int i = 0; for (; i < attributes.length(); i++ ) { QDomAttr attr = attributes.item( i ).toAttr(); if ( attr.name().toLower() == d->attrLineSeparator && attr.value().toLower() == QStringLiteral("false") ) { isLineSep = false; break; } } if ( isLineSep ) return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L ); else*/ return bar->insertSeparator(before); } } else if (tagName == d->tagTearOffHandle) { static_cast(parent)->setTearOffEnabled(true); } else if (tagName == d->tagMenuTitle) { if (QMenu *m = qobject_cast(parent)) { QString i18nText; const QString text = element.text(); if (text.isEmpty()) { i18nText = i18n("No text"); } else { QByteArray domain = element.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } i18nText = i18nd(domain.constData(), qPrintable(text)); } QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = KisIconUtils::loadIcon(icon); } if (!icon.isEmpty()) { return m->insertSection(before, pix, i18nText); } else { return m->insertSection(before, i18nText); } } } QAction *blank = new QAction(parent); blank->setVisible(false); parent->insertAction(before, blank); return blank; } void KXMLGUIBuilder::removeCustomElement(QWidget *parent, QAction *action) { parent->removeAction(action); } KXMLGUIClient *KXMLGUIBuilder::builderClient() const { return d->m_client; } void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client) { d->m_client = client; } void KXMLGUIBuilder::finalizeGUI(KXMLGUIClient *) { KXmlGuiWindow *window = qobject_cast(d->m_widget); if (!window) { return; } #if 0 KToolBar *toolbar = 0; QListIterator it(((KMainWindow *)d->m_widget)->toolBarIterator()); while ((toolbar = it.current())) { //qDebug(260) << "KXMLGUIBuilder::finalizeGUI toolbar=" << (void*)toolbar; ++it; toolbar->positionYourself(); } #else window->finalizeGUI(false); #endif } void KXMLGUIBuilder::virtual_hook(int, void *) { /*BASE::virtual_hook( id, data );*/ } diff --git a/plugins/dockers/animation/kis_animation_curves_model.cpp b/plugins/dockers/animation/kis_animation_curves_model.cpp index a61751a9dc..ea1d826dc0 100644 --- a/plugins/dockers/animation/kis_animation_curves_model.cpp +++ b/plugins/dockers/animation/kis_animation_curves_model.cpp @@ -1,412 +1,413 @@ /* * Copyright (c) 2016 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_curves_model.h" #include #include "kis_global.h" #include "kis_image.h" #include "kis_node.h" #include "kis_keyframe_channel.h" #include "kis_scalar_keyframe_channel.h" #include "kis_post_execution_undo_adapter.h" #include "kis_animation_utils.h" #include "kis_processing_applicator.h" #include "kis_command_utils.h" #include "KisImageBarrierLockerWithFeedback.h" struct KisAnimationCurve::Private { Private(KisScalarKeyframeChannel *channel, QColor color) : channel(channel) , color(color) , visible(true) {} KisScalarKeyframeChannel *channel; QColor color; bool visible; }; KisAnimationCurve::KisAnimationCurve(KisScalarKeyframeChannel *channel, QColor color) : m_d(new Private(channel, color)) {} KisScalarKeyframeChannel *KisAnimationCurve::channel() const { return m_d->channel; } QColor KisAnimationCurve::color() const { return m_d->color; } void KisAnimationCurve::setVisible(bool visible) { m_d->visible = visible; } bool KisAnimationCurve::visible() const { return m_d->visible; } struct KisAnimationCurvesModel::Private { QList curves; int nextColorHue; KUndo2Command *undoCommand; Private() : nextColorHue(0) , undoCommand(0) {} KisAnimationCurve *getCurveAt(const QModelIndex& index) { if (!index.isValid()) return 0; int row = index.row(); if (row < 0 || row >= curves.size()) { return 0; } return curves.at(row); } int rowForCurve(KisAnimationCurve *curve) { return curves.indexOf(curve); } int rowForChannel(KisKeyframeChannel *channel) { for (int row = 0; row < curves.count(); row++) { if (curves.at(row)->channel() == channel) return row; } return -1; } QColor chooseNextColor() { if (curves.isEmpty()) nextColorHue = 0; QColor color = QColor::fromHsv(nextColorHue, 255, 255); nextColorHue += 94; // Value chosen experimentally for providing distinct colors nextColorHue = nextColorHue & 0xff; return color; } }; KisAnimationCurvesModel::KisAnimationCurvesModel(QObject *parent) : KisTimeBasedItemModel(parent) , m_d(new Private()) {} KisAnimationCurvesModel::~KisAnimationCurvesModel() { qDeleteAll(m_d->curves); } int KisAnimationCurvesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_d->curves.size(); } QVariant KisAnimationCurvesModel::data(const QModelIndex &index, int role) const { KisAnimationCurve *curve = m_d->getCurveAt(index); if (curve) { KisScalarKeyframeChannel *channel = curve->channel(); int time = index.column(); KisKeyframeSP keyframe = channel->keyframeAt(time); switch (role) { case SpecialKeyframeExists: return !keyframe.isNull(); case ScalarValueRole: return channel->interpolatedValue(time); case LeftTangentRole: return (keyframe.isNull()) ? QVariant() : keyframe->leftTangent(); case RightTangentRole: return (keyframe.isNull()) ? QVariant() : keyframe->rightTangent(); case InterpolationModeRole: return (keyframe.isNull()) ? QVariant() : keyframe->interpolationMode(); case TangentsModeRole: return (keyframe.isNull()) ? QVariant() : keyframe->tangentsMode(); case CurveColorRole: return curve->color(); case CurveVisibleRole: return curve->visible(); case PreviousKeyframeTime: { KisKeyframeSP active = channel->activeKeyframeAt(time); if (active.isNull()) return QVariant(); if (active->time() < time) { return active->time(); } KisKeyframeSP previous = channel->previousKeyframe(active); if (previous.isNull()) return QVariant(); return previous->time(); } case NextKeyframeTime: { KisKeyframeSP active = channel->activeKeyframeAt(time); if (active.isNull()) { KisKeyframeSP first = channel->firstKeyframe(); if (!first.isNull() && first->time() > time) { return first->time(); } return QVariant(); } KisKeyframeSP next = channel->nextKeyframe(active); if (next.isNull()) return QVariant(); return next->time(); } default: break; } } return KisTimeBasedItemModel::data(index, role); } bool KisAnimationCurvesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel(); KUndo2Command *command = m_d->undoCommand; switch (role) { case ScalarValueRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (keyframe) { if (!command) command = new KUndo2Command(kundo2_i18n("Adjust keyframe")); channel->setScalarValue(keyframe, value.toReal(), command); } else { if (!command) command = new KUndo2Command(kundo2_i18n("Insert keyframe")); auto *addKeyframeCommand = new KisScalarKeyframeChannel::AddKeyframeCommand( channel, index.column(), value.toReal(), command); addKeyframeCommand->redo(); } } break; case LeftTangentRole: case RightTangentRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (!keyframe) return false; QPointF leftTangent = (role == LeftTangentRole ? value.toPointF() : keyframe->leftTangent()); QPointF rightTangent = (role == RightTangentRole ? value.toPointF() : keyframe->rightTangent()); if (!command) command = new KUndo2Command(kundo2_i18n("Adjust tangent")); channel->setInterpolationTangents(keyframe, keyframe->tangentsMode(), leftTangent, rightTangent, command); } break; case InterpolationModeRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (!keyframe) return false; if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode")); channel->setInterpolationMode(keyframe, (KisKeyframe::InterpolationMode)value.toInt(), command); } break; case TangentsModeRole: { KisKeyframeSP keyframe = channel->keyframeAt(index.column()); if (!keyframe) return false; KisKeyframe::InterpolationTangentsMode mode = (KisKeyframe::InterpolationTangentsMode)value.toInt(); QPointF leftTangent = keyframe->leftTangent(); QPointF rightTangent = keyframe->rightTangent(); if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode")); channel->setInterpolationTangents(keyframe, mode, leftTangent, rightTangent, command); } break; default: return KisTimeBasedItemModel::setData(index, value, role); } if (command && !m_d->undoCommand) { image()->postExecutionUndoAdapter()->addCommand(toQShared(command)); } return true; } QVariant KisAnimationCurvesModel::headerData(int section, Qt::Orientation orientation, int role) const { // TODO return KisTimeBasedItemModel::headerData(section, orientation, role); } void KisAnimationCurvesModel::beginCommand(const KUndo2MagicString &text) { KIS_ASSERT_RECOVER_RETURN(!m_d->undoCommand); m_d->undoCommand = new KUndo2Command(text); } void KisAnimationCurvesModel::endCommand() { KIS_ASSERT_RECOVER_RETURN(m_d->undoCommand); image()->postExecutionUndoAdapter()->addCommand(toQShared(m_d->undoCommand)); m_d->undoCommand = 0; } bool KisAnimationCurvesModel::adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset) { QScopedPointer command( new KUndo2Command( kundo2_i18np("Adjust Keyframe", "Adjust %1 Keyframes", indexes.size()))); { KisImageBarrierLockerWithFeedback locker(image()); if (timeOffset != 0) { bool ok = createOffsetFramesCommand(indexes, QPoint(timeOffset, 0), false, command.data()); if (!ok) return false; } using KisAnimationUtils::FrameItem; using KisAnimationUtils::FrameItemList; FrameItemList frameItems; Q_FOREACH(QModelIndex index, indexes) { KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel(); KIS_ASSERT_RECOVER_RETURN_VALUE(channel, false); frameItems << FrameItem(channel->node(), channel->id(), index.column() + timeOffset); }; new KisCommandUtils::LambdaCommand( command.data(), [frameItems, valueOffset] () -> KUndo2Command* { QScopedPointer cmd(new KUndo2Command()); bool result = false; Q_FOREACH (const FrameItem &item, frameItems) { const int time = item.time; KisNodeSP node = item.node; KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel); if (!channel) continue; KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) continue; const qreal currentValue = channel->scalarValue(keyframe); channel->setScalarValue(keyframe, currentValue + valueOffset, cmd.data()); result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); } KisProcessingApplicator::runSingleCommandStroke(image(), command.take(), KisStrokeJobData::BARRIER); return true; } KisAnimationCurve *KisAnimationCurvesModel::addCurve(KisScalarKeyframeChannel *channel) { beginInsertRows(QModelIndex(), m_d->curves.size(), m_d->curves.size()); KisAnimationCurve *curve = new KisAnimationCurve(channel, m_d->chooseNextColor()); m_d->curves.append(curve); endInsertRows(); connect(channel, &KisScalarKeyframeChannel::sigKeyframeAdded, this, &KisAnimationCurvesModel::slotKeyframeChanged); connect(channel, &KisScalarKeyframeChannel::sigKeyframeMoved, this, &KisAnimationCurvesModel::slotKeyframeChanged); connect(channel, &KisScalarKeyframeChannel::sigKeyframeRemoved, this, &KisAnimationCurvesModel::slotKeyframeChanged); connect(channel, &KisScalarKeyframeChannel::sigKeyframeChanged, this, &KisAnimationCurvesModel::slotKeyframeChanged); return curve; } void KisAnimationCurvesModel::removeCurve(KisAnimationCurve *curve) { int index = m_d->curves.indexOf(curve); if (index < 0) return; curve->channel()->disconnect(this); beginRemoveRows(QModelIndex(), index, index); m_d->curves.removeAt(index); delete curve; endRemoveRows(); } void KisAnimationCurvesModel::setCurveVisible(KisAnimationCurve *curve, bool visible) { curve->setVisible(visible); int row = m_d->rowForCurve(curve); emit dataChanged(index(row, 0), index(row, columnCount())); } KisNodeSP KisAnimationCurvesModel::nodeAt(QModelIndex index) const { KisAnimationCurve *curve = m_d->getCurveAt(index); if (curve && curve->channel() && curve->channel()->node()) { return KisNodeSP(curve->channel()->node()); } return 0; } -QList KisAnimationCurvesModel::channelsAt(QModelIndex index) const +QMap KisAnimationCurvesModel::channelsAt(QModelIndex index) const { KisKeyframeChannel *channel = m_d->getCurveAt(index)->channel(); - QList list({channel}); + QMap list; + list[""] = channel; return list; } void KisAnimationCurvesModel::slotKeyframeChanged(KisKeyframeSP keyframe) { int row = m_d->rowForChannel(keyframe->channel()); QModelIndex changedIndex = index(row, keyframe->time()); emit dataChanged(changedIndex, changedIndex); } diff --git a/plugins/dockers/animation/kis_animation_curves_model.h b/plugins/dockers/animation/kis_animation_curves_model.h index 919dcb5fda..f09ee02f20 100644 --- a/plugins/dockers/animation/kis_animation_curves_model.h +++ b/plugins/dockers/animation/kis_animation_curves_model.h @@ -1,98 +1,98 @@ /* * Copyright (c) 2016 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. */ #ifndef _KIS_ANIMATION_CURVES_MODEL_H #define _KIS_ANIMATION_CURVES_MODEL_H #include #include #include #include "kis_time_based_item_model.h" #include "kis_types.h" #include "kundo2command.h" class KisScalarKeyframeChannel; class KisAnimationCurve { public: KisAnimationCurve(KisScalarKeyframeChannel *channel, QColor color); KisScalarKeyframeChannel *channel() const; QColor color() const; void setVisible(bool visible); bool visible() const; private: struct Private; const QScopedPointer m_d; }; class KisAnimationCurvesModel : public KisTimeBasedItemModel { Q_OBJECT public: KisAnimationCurvesModel(QObject *parent); ~KisAnimationCurvesModel() override; bool hasConnectionToCanvas() const; KisAnimationCurve *addCurve(KisScalarKeyframeChannel *channel); void removeCurve(KisAnimationCurve *curve); void setCurveVisible(KisAnimationCurve *curve, bool visible); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; /** * Begins a block of commands. The following calls to setData will be grouped to a single undo step. * Note: MUST be followed by a call to endCommand(). */ void beginCommand(const KUndo2MagicString &text); void endCommand(); bool adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset); enum ItemDataRole { ScalarValueRole = KisTimeBasedItemModel::UserRole + 101, InterpolationModeRole, TangentsModeRole, LeftTangentRole, RightTangentRole, CurveColorRole, CurveVisibleRole, PreviousKeyframeTime, NextKeyframeTime }; protected: KisNodeSP nodeAt(QModelIndex index) const override; - QList channelsAt(QModelIndex index) const override; + QMap channelsAt(QModelIndex index) const override; private Q_SLOTS: void slotKeyframeChanged(KisKeyframeSP keyframe); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp index 6376856feb..55b158cdee 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.cpp +++ b/plugins/dockers/animation/kis_time_based_item_model.cpp @@ -1,439 +1,436 @@ /* * Copyright (c) 2016 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_time_based_item_model.h" #include #include #include "kis_animation_frame_cache.h" #include "kis_animation_player.h" #include "kis_signal_compressor_with_param.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_animation_utils.h" #include "kis_keyframe_channel.h" #include "kis_processing_applicator.h" #include "KisImageBarrierLockerWithFeedback.h" struct KisTimeBasedItemModel::Private { Private() : animationPlayer(0) , numFramesOverride(0) , activeFrameIndex(0) , scrubInProgress(false) , scrubStartFrame(-1) {} KisImageWSP image; KisAnimationFrameCacheWSP framesCache; QPointer animationPlayer; QVector cachedFrames; int numFramesOverride; int activeFrameIndex; bool scrubInProgress; int scrubStartFrame; QScopedPointer > scrubbingCompressor; int baseNumFrames() const { if (image.isNull()) return 0; KisImageAnimationInterface *i = image->animationInterface(); if (!i) return 1; return i->totalLength(); } int effectiveNumFrames() const { if (image.isNull()) return 0; return qMax(baseNumFrames(), numFramesOverride); } int framesPerSecond() { return image->animationInterface()->framerate(); } }; KisTimeBasedItemModel::KisTimeBasedItemModel(QObject *parent) : QAbstractTableModel(parent) , m_d(new Private()) { KisConfig cfg; using namespace std::placeholders; std::function callback( std::bind(&KisTimeBasedItemModel::slotInternalScrubPreviewRequested, this, _1)); m_d->scrubbingCompressor.reset( new KisSignalCompressorWithParam(cfg.scrubbingUpdatesDelay(), callback, KisSignalCompressor::FIRST_ACTIVE)); } KisTimeBasedItemModel::~KisTimeBasedItemModel() {} void KisTimeBasedItemModel::setImage(KisImageWSP image) { KisImageWSP oldImage = m_d->image; m_d->image = image; if (image) { KisImageAnimationInterface *ai = image->animationInterface(); slotCurrentTimeChanged(ai->currentUITime()); connect(ai, SIGNAL(sigFramerateChanged()), SLOT(slotFramerateChanged())); connect(ai, SIGNAL(sigUiTimeChanged(int)), SLOT(slotCurrentTimeChanged(int))); } if (image != oldImage) { reset(); } } void KisTimeBasedItemModel::setFrameCache(KisAnimationFrameCacheSP cache) { if (KisAnimationFrameCacheSP(m_d->framesCache) == cache) return; if (m_d->framesCache) { m_d->framesCache->disconnect(this); } m_d->framesCache = cache; if (m_d->framesCache) { connect(m_d->framesCache, SIGNAL(changed()), SLOT(slotCacheChanged())); } } void KisTimeBasedItemModel::setAnimationPlayer(KisAnimationPlayer *player) { if (m_d->animationPlayer == player) return; if (m_d->animationPlayer) { m_d->animationPlayer->disconnect(this); } m_d->animationPlayer = player; if (m_d->animationPlayer) { connect(m_d->animationPlayer, SIGNAL(sigPlaybackStopped()), SLOT(slotPlaybackStopped())); connect(m_d->animationPlayer, SIGNAL(sigFrameChanged()), SLOT(slotPlaybackFrameChanged())); } } void KisTimeBasedItemModel::setLastVisibleFrame(int time) { const int growThreshold = m_d->effectiveNumFrames() - 3; const int growValue = time + 8; const int shrinkThreshold = m_d->effectiveNumFrames() - 12; const int shrinkValue = qMax(m_d->baseNumFrames(), qMin(growValue, shrinkThreshold)); const bool canShrink = m_d->effectiveNumFrames() > m_d->baseNumFrames(); if (time >= growThreshold) { beginInsertColumns(QModelIndex(), m_d->effectiveNumFrames(), growValue - 1); m_d->numFramesOverride = growValue; endInsertColumns(); } else if (time < shrinkThreshold && canShrink) { beginRemoveColumns(QModelIndex(), shrinkValue, m_d->effectiveNumFrames() - 1); m_d->numFramesOverride = shrinkValue; endRemoveColumns(); } } int KisTimeBasedItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_d->effectiveNumFrames(); } QVariant KisTimeBasedItemModel::data(const QModelIndex &index, int role) const { switch (role) { case ActiveFrameRole: { return index.column() == m_d->activeFrameIndex; } } return QVariant(); } bool KisTimeBasedItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; switch (role) { case ActiveFrameRole: { setHeaderData(index.column(), Qt::Horizontal, value, role); break; } } return false; } QVariant KisTimeBasedItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: return section == m_d->activeFrameIndex; case FrameCachedRole: return m_d->cachedFrames.size() > section ? m_d->cachedFrames[section] : false; case FramesPerSecondRole: return m_d->framesPerSecond(); } } return QVariant(); } bool KisTimeBasedItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: if (value.toBool() && section != m_d->activeFrameIndex) { int prevFrame = m_d->activeFrameIndex; m_d->activeFrameIndex = section; scrubTo(m_d->activeFrameIndex, m_d->scrubInProgress); /** * Optimization Hack Alert: * * ideally, we should emit all four signals, but... The * point is this code is used in a tight loop during * playback, so it should run as fast as possible. To tell * the story short, commenting out these three lines makes * playback run 15% faster ;) */ if (m_d->scrubInProgress) { //emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); //emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); //emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } else { emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } } } } return false; } bool KisTimeBasedItemModel::removeFrames(const QModelIndexList &indexes) { KisAnimationUtils::FrameItemList frameItems; { KisImageBarrierLockerWithFeedback locker(m_d->image); Q_FOREACH (const QModelIndex &index, indexes) { int time = index.column(); - - QList channels = channelsAt(index); - Q_FOREACH(KisKeyframeChannel *channel, channels) { + Q_FOREACH(KisKeyframeChannel *channel, channelsAt(index)) { if (channel->keyframeAt(time)) { frameItems << KisAnimationUtils::FrameItem(channel->node(), channel->id(), index.column()); } } } } if (frameItems.isEmpty()) return false; KisAnimationUtils::removeKeyframes(m_d->image, frameItems); return true; } KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand) { if (srcIndexes.isEmpty()) return 0; if (offset.isNull()) return 0; KisAnimationUtils::sortPointsForSafeMove(&srcIndexes, offset); KisAnimationUtils::FrameItemList srcFrameItems; KisAnimationUtils::FrameItemList dstFrameItems; Q_FOREACH (const QModelIndex &srcIndex, srcIndexes) { QModelIndex dstIndex = index( srcIndex.row() + offset.y(), srcIndex.column() + offset.x()); KisNodeSP srcNode = nodeAt(srcIndex); KisNodeSP dstNode = nodeAt(dstIndex); if (!srcNode || !dstNode) { return 0; } - QList channels = channelsAt(srcIndex); - Q_FOREACH(KisKeyframeChannel *channel, channels) { + Q_FOREACH(KisKeyframeChannel *channel, channelsAt(srcIndex)) { if (channel->keyframeAt(srcIndex.column())) { srcFrameItems << KisAnimationUtils::FrameItem(srcNode, channel->id(), srcIndex.column()); dstFrameItems << KisAnimationUtils::FrameItem(dstNode, channel->id(), dstIndex.column()); } } } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcFrameItems.size() == dstFrameItems.size(), 0); if (srcFrameItems.isEmpty()) return 0; return KisAnimationUtils::createMoveKeyframesCommand(srcFrameItems, dstFrameItems, copyFrames, parentCommand); } bool KisTimeBasedItemModel::offsetFrames(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames) { KUndo2Command *cmd = 0; { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = createOffsetFramesCommand(srcIndexes, offset, copyFrames); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER); } return cmd; } void KisTimeBasedItemModel::slotInternalScrubPreviewRequested(int time) { if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->displayFrame(time); } } void KisTimeBasedItemModel::setScrubState(bool active) { if (!m_d->scrubInProgress && active) { m_d->scrubStartFrame = m_d->activeFrameIndex; m_d->scrubInProgress = true; } if (m_d->scrubInProgress && !active) { m_d->scrubInProgress = false; if (m_d->scrubStartFrame >= 0 && m_d->scrubStartFrame != m_d->activeFrameIndex) { scrubTo(m_d->activeFrameIndex, false); } m_d->scrubStartFrame = -1; } } void KisTimeBasedItemModel::scrubTo(int time, bool preview) { if (m_d->animationPlayer && m_d->animationPlayer->isPlaying()) return; KIS_ASSERT_RECOVER_RETURN(m_d->image); if (preview) { if (m_d->animationPlayer) { m_d->scrubbingCompressor->start(time); } } else { m_d->image->animationInterface()->requestTimeSwitchWithUndo(time); } } void KisTimeBasedItemModel::slotFramerateChanged() { emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); } void KisTimeBasedItemModel::slotCurrentTimeChanged(int time) { if (time != m_d->activeFrameIndex) { setHeaderData(time, Qt::Horizontal, true, ActiveFrameRole); } } void KisTimeBasedItemModel::slotCacheChanged() { const int numFrames = columnCount(); m_d->cachedFrames.resize(numFrames); for (int i = 0; i < numFrames; i++) { m_d->cachedFrames[i] = m_d->framesCache->frameStatus(i) == KisAnimationFrameCache::Cached; } emit headerDataChanged(Qt::Horizontal, 0, numFrames); } void KisTimeBasedItemModel::slotPlaybackFrameChanged() { if (!m_d->animationPlayer->isPlaying()) return; setData(index(0, m_d->animationPlayer->currentTime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::slotPlaybackStopped() { setData(index(0, m_d->image->animationInterface()->currentUITime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::setPlaybackRange(const KisTimeRange &range) { if (m_d->image.isNull()) return; KisImageAnimationInterface *i = m_d->image->animationInterface(); i->setPlaybackRange(range); } bool KisTimeBasedItemModel::isPlaybackActive() const { return m_d->animationPlayer && m_d->animationPlayer->isPlaying(); } int KisTimeBasedItemModel::currentTime() const { return m_d->image->animationInterface()->currentUITime(); } KisImageWSP KisTimeBasedItemModel::image() const { return m_d->image; } diff --git a/plugins/dockers/animation/kis_time_based_item_model.h b/plugins/dockers/animation/kis_time_based_item_model.h index 72cb3382b8..08428c54fa 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.h +++ b/plugins/dockers/animation/kis_time_based_item_model.h @@ -1,98 +1,98 @@ /* * Copyright (c) 2016 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. */ #ifndef _KIS_TIME_BASED_ITEM_MODEL_H #define _KIS_TIME_BASED_ITEM_MODEL_H #include #include #include "kritaanimationdocker_export.h" #include "kis_types.h" class KisTimeRange; class KisAnimationPlayer; class KisKeyframeChannel; class KRITAANIMATIONDOCKER_EXPORT KisTimeBasedItemModel : public QAbstractTableModel { Q_OBJECT public: KisTimeBasedItemModel(QObject *parent); ~KisTimeBasedItemModel() override; void setImage(KisImageWSP image); void setFrameCache(KisAnimationFrameCacheSP cache); void setAnimationPlayer(KisAnimationPlayer *player); void setLastVisibleFrame(int time); int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; bool removeFrames(const QModelIndexList &indexes); bool offsetFrames(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames); void setScrubState(bool active); void scrubTo(int time, bool preview); void setPlaybackRange(const KisTimeRange &range); bool isPlaybackActive() const; int currentTime() const; enum ItemDataRole { ActiveFrameRole = Qt::UserRole + 101, FrameExistsRole, SpecialKeyframeExists, FrameCachedRole, FrameEditableRole, FramesPerSecondRole, UserRole }; protected: virtual KisNodeSP nodeAt(QModelIndex index) const = 0; - virtual QList channelsAt(QModelIndex index) const = 0; + virtual QMap channelsAt(QModelIndex index) const = 0; KisImageWSP image() const; KUndo2Command* createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand = 0); private Q_SLOTS: void slotFramerateChanged(); void slotCurrentTimeChanged(int time); void slotCacheChanged(); void slotInternalScrubPreviewRequested(int time); void slotPlaybackFrameChanged(); void slotPlaybackStopped(); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index f23f5c3b59..e42bb122f5 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,720 +1,717 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; - - QList channels = dummy->node()->keyframeChannels(); - - Q_FOREACH(KisKeyframeChannel *channel, channels) { + Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodePropertyListCommand::setNodePropertiesNoUndo(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } -QList TimelineFramesModel::channelsAt(QModelIndex index) const +QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { reset(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::processUpdateQueue() { Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); KIS_ASSERT_RECOVER_RETURN(dummy); m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case KoResourceModel::LargeThumbnailRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; QSize size = dummy->node()->extent().size(); size.scale(maxSize, maxSize, Qt::KeepAspectRatio); if (size.width() == 0 || size.height() == 0) { // No thumbnail can be shown if there isn't width or height... return QVariant(); } QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedMask()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = m_d->lastClickedIndex.row(); const int baseColumn = m_d->lastClickedIndex.column(); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { stream << index.row() - baseRow << index.column() - baseColumn; } data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; const bool copyFrames = action == Qt::CopyAction; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; QModelIndexList srcIndexes; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; int srcRow = baseRow + relRow; int srcColumn = baseColumn + relColumn; srcIndexes << index(srcRow, srcColumn); } const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); return offsetFrames(srcIndexes, offset, copyFrames); } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h index 1c39151c3c..af086b1167 100644 --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -1,132 +1,132 @@ /* * 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 __TIMELINE_FRAMES_MODEL_H #define __TIMELINE_FRAMES_MODEL_H #include #include #include "kritaanimationdocker_export.h" #include "kis_node_model.h" #include "kis_types.h" #include "kis_node.h" #include "timeline_node_list_keeper.h" class KisNodeDummy; class KisDummiesFacadeBase; class KisAnimationPlayer; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesModel : public TimelineNodeListKeeper::ModelWithExternalNotifications { Q_OBJECT public: TimelineFramesModel(QObject *parent); ~TimelineFramesModel() override; bool hasConnectionToCanvas() const; void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image); bool canDropFrameData(const QMimeData *data, const QModelIndex &index); bool insertOtherLayer(int index, int dstRow); int activeLayerRow() const; bool createFrame(const QModelIndex &dstIndex); bool copyFrame(const QModelIndex &dstIndex); QString audioChannelFileName() const; void setAudioChannelFileName(const QString &fileName); bool isAudioMuted() const; void setAudioMuted(bool value); qreal audioVolume() const; void setAudioVolume(qreal value); void setLastClickedIndex(const QModelIndex &index); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; QMimeData * mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool insertRows(int row, int count, const QModelIndex &parent) override; bool removeRows(int row, int count, const QModelIndex &parent) override; enum ItemDataRole { ActiveLayerRole = KisTimeBasedItemModel::UserRole, TimelinePropertiesRole, OtherLayersRole, LayerUsedInTimelineRole, FrameColorLabelIndexRole }; // metatype is added by the original implementation typedef KisBaseNode::Property Property; typedef KisBaseNode::PropertyList PropertyList; typedef TimelineNodeListKeeper::OtherLayer OtherLayer; typedef TimelineNodeListKeeper::OtherLayersList OtherLayersList; struct NodeManipulationInterface { virtual ~NodeManipulationInterface() {} virtual KisLayerSP addPaintLayer() const = 0; virtual void removeNode(KisNodeSP node) const = 0; }; /** * NOTE: the model has an ownership over the interface, that is it'll * be deleted automatically later */ void setNodeManipulationInterface(NodeManipulationInterface *iface); protected: KisNodeSP nodeAt(QModelIndex index) const override; - QList channelsAt(QModelIndex index) const override; + QMap channelsAt(QModelIndex index) const override; private Q_SLOTS: void slotDummyChanged(KisNodeDummy *dummy); void processUpdateQueue(); public Q_SLOTS: void slotCurrentNodeChanged(KisNodeSP node); Q_SIGNALS: void requestCurrentNodeChanged(KisNodeSP node); void sigInfiniteTimelineUpdateNeeded(); void sigAudioChannelChanged(); void sigEnsureRowVisible(int row); - + private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_MODEL_H */ diff --git a/plugins/dockers/animation/timeline_node_list_keeper.cpp b/plugins/dockers/animation/timeline_node_list_keeper.cpp index 9ab50a39f6..98b07c8d47 100644 --- a/plugins/dockers/animation/timeline_node_list_keeper.cpp +++ b/plugins/dockers/animation/timeline_node_list_keeper.cpp @@ -1,249 +1,249 @@ /* * 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_node_list_keeper.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "timeline_frames_index_converter.h" #include #include #include "kis_keyframe_channel.h" struct TimelineNodeListKeeper::Private { Private(TimelineNodeListKeeper *_q, ModelWithExternalNotifications *_model, KisDummiesFacadeBase *_dummiesFacade) : q(_q), model(_model), dummiesFacade(_dummiesFacade), converter(dummiesFacade) { } TimelineNodeListKeeper *q; ModelWithExternalNotifications *model; KisDummiesFacadeBase *dummiesFacade; TimelineFramesIndexConverter converter; QVector dummiesList; QSignalMapper dummiesUpdateMapper; QSet connectionsSet; void populateDummiesList() { const int rowCount = converter.rowCount(); for (int i = 0; i < rowCount; ++i) { KisNodeDummy *dummy = converter.dummyFromRow(i); dummiesList.append(dummy); tryConnectDummy(dummy); } } void tryConnectDummy(KisNodeDummy *dummy); void disconnectDummy(KisNodeDummy *dummy); }; TimelineNodeListKeeper::TimelineNodeListKeeper(ModelWithExternalNotifications *model, KisDummiesFacadeBase *dummiesFacade) : m_d(new Private(this, model, dummiesFacade)) { KIS_ASSERT_RECOVER_RETURN(m_d->dummiesFacade); connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), SLOT(slotEndInsertDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)), SLOT(slotBeginRemoveDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); m_d->populateDummiesList(); connect(&m_d->dummiesUpdateMapper, SIGNAL(mapped(QObject*)), SLOT(slotUpdateDummyContent(QObject*))); } TimelineNodeListKeeper::~TimelineNodeListKeeper() { } KisNodeDummy* TimelineNodeListKeeper::dummyFromRow(int row) { if (row >= 0 && row < m_d->dummiesList.size()) { return m_d->dummiesList[row]; } return 0; } int TimelineNodeListKeeper::rowForDummy(KisNodeDummy *dummy) { return m_d->dummiesList.indexOf(dummy); } int TimelineNodeListKeeper::rowCount() { return m_d->dummiesList.size(); } void TimelineNodeListKeeper::updateActiveDummy(KisNodeDummy *dummy) { bool oldRemoved = false; bool newAdded = false; KisNodeDummy *oldActiveDummy = m_d->converter.activeDummy(); m_d->converter.updateActiveDummy(dummy, &oldRemoved, &newAdded); if (oldRemoved) { slotBeginRemoveDummy(oldActiveDummy); } if (newAdded) { slotEndInsertDummy(dummy); } } void TimelineNodeListKeeper::slotUpdateDummyContent(QObject *_dummy) { KisNodeDummy *dummy = qobject_cast(_dummy); int pos = m_d->converter.rowForDummy(dummy); if (pos < 0) return; QModelIndex index0 = m_d->model->index(pos, 0); QModelIndex index1 = m_d->model->index(pos, m_d->model->columnCount() - 1); m_d->model->callIndexChanged(index0, index1); } void TimelineNodeListKeeper::Private::tryConnectDummy(KisNodeDummy *dummy) { - QList channels = dummy->node()->keyframeChannels(); + QMap channels = dummy->node()->keyframeChannels(); if (channels.isEmpty()) { if (connectionsSet.contains(dummy)) { connectionsSet.remove(dummy); } return; } if (connectionsSet.contains(dummy)) return; Q_FOREACH(KisKeyframeChannel *channel, channels) { connect(channel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), &dummiesUpdateMapper, SLOT(map())); connect(channel, SIGNAL(sigKeyframeAboutToBeRemoved(KisKeyframeSP)), &dummiesUpdateMapper, SLOT(map())); connect(channel, SIGNAL(sigKeyframeMoved(KisKeyframeSP, int)), &dummiesUpdateMapper, SLOT(map())); dummiesUpdateMapper.setMapping(channel, (QObject*)dummy); } connectionsSet.insert(dummy); } void TimelineNodeListKeeper::Private::disconnectDummy(KisNodeDummy *dummy) { if (!connectionsSet.contains(dummy)) return; - QList channels = dummy->node()->keyframeChannels(); + QMap channels = dummy->node()->keyframeChannels(); if (channels.isEmpty()) { if (connectionsSet.contains(dummy)) { connectionsSet.remove(dummy); } return; } Q_FOREACH(KisKeyframeChannel *channel, channels) { channel->disconnect(&dummiesUpdateMapper); } connectionsSet.remove(dummy); } void TimelineNodeListKeeper::slotEndInsertDummy(KisNodeDummy *dummy) { KIS_ASSERT_RECOVER_RETURN(!m_d->dummiesList.contains(dummy)); if (m_d->converter.isDummyVisible(dummy)) { int pos = m_d->converter.rowForDummy(dummy); m_d->model->callBeginInsertRows(QModelIndex(), pos, pos); m_d->dummiesList.insert(pos, 1, dummy); m_d->tryConnectDummy(dummy); m_d->model->callEndInsertRows(); } } void TimelineNodeListKeeper::slotBeginRemoveDummy(KisNodeDummy *dummy) { if (m_d->dummiesList.contains(dummy)) { int pos = m_d->dummiesList.indexOf(dummy); m_d->model->callBeginRemoveRows(QModelIndex(), pos, pos); m_d->disconnectDummy(dummy); m_d->dummiesList.remove(pos); m_d->model->callEndRemoveRows(); } m_d->converter.notifyDummyRemoved(dummy); } void TimelineNodeListKeeper::slotDummyChanged(KisNodeDummy *dummy) { const bool present = m_d->dummiesList.contains(dummy); const bool shouldBe = m_d->converter.isDummyVisible(dummy); m_d->tryConnectDummy(dummy); if (!present && shouldBe) { slotEndInsertDummy(dummy); } else if (present && !shouldBe) { slotBeginRemoveDummy(dummy); } } void findOtherLayers(KisNodeDummy *root, TimelineNodeListKeeper::OtherLayersList *list, const QString &prefix) { KisNodeSP node = root->node(); if (root->parent() && !node->useInTimeline()) { *list << TimelineNodeListKeeper::OtherLayer( QString(prefix + node->name()), root); } KisNodeDummy *dummy = root->lastChild(); while(dummy) { findOtherLayers(dummy, list, prefix + " "); dummy = dummy->prevSibling(); } } TimelineNodeListKeeper::OtherLayersList TimelineNodeListKeeper::otherLayersList() const { OtherLayersList list; findOtherLayers(m_d->dummiesFacade->rootDummy(), &list, ""); return list; } diff --git a/plugins/extensions/CMakeLists.txt b/plugins/extensions/CMakeLists.txt index 4c4dff7f5a..cdf8475a9f 100644 --- a/plugins/extensions/CMakeLists.txt +++ b/plugins/extensions/CMakeLists.txt @@ -1,28 +1,29 @@ add_subdirectory( bigbrother ) add_subdirectory( imagesplit ) add_subdirectory( clonesarray ) add_subdirectory( colorrange ) add_subdirectory( colorspaceconversion ) add_subdirectory( histogram ) add_subdirectory( imagesize ) add_subdirectory( metadataeditor ) add_subdirectory( modify_selection ) add_subdirectory( offsetimage ) add_subdirectory( rotateimage ) add_subdirectory( separate_channels ) add_subdirectory( shearimage ) add_subdirectory( layergroupswitcher ) add_subdirectory( resourcemanager ) add_subdirectory( layersplit ) add_subdirectory( animationrenderer ) add_subdirectory( waveletdecompose ) # Allow to skip building GMIC plugin option(WITH_GMIC "Build the G'Mic plugin" ON) if(WITH_GMIC) if (CMAKE_COMPILER_IS_GNUCC) add_subdirectory( gmic ) endif() endif() add_subdirectory( pykrita ) +add_subdirectory( buginfo ) diff --git a/plugins/extensions/buginfo/CMakeLists.txt b/plugins/extensions/buginfo/CMakeLists.txt new file mode 100644 index 0000000000..a102ed6838 --- /dev/null +++ b/plugins/extensions/buginfo/CMakeLists.txt @@ -0,0 +1,10 @@ +set(kritabuginfo_SOURCES + buginfo.cpp + dlg_buginfo.cpp +) + +ki18n_wrap_ui(kritabuginfo_SOURCES wdg_buginfo.ui ) +add_library(kritabuginfo MODULE ${kritabuginfo_SOURCES}) +target_link_libraries(kritabuginfo kritaui) +install(TARGETS kritabuginfo DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) +install( FILES buginfo.xmlgui DESTINATION ${DATA_INSTALL_DIR}/kritaplugins) diff --git a/plugins/extensions/buginfo/buginfo.cpp b/plugins/extensions/buginfo/buginfo.cpp new file mode 100644 index 0000000000..a861f03049 --- /dev/null +++ b/plugins/extensions/buginfo/buginfo.cpp @@ -0,0 +1,52 @@ +/* + * 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 "buginfo.h" + +#include + +#include +#include +#include +#include +#include +#include +#include "dlg_buginfo.h" + +K_PLUGIN_FACTORY_WITH_JSON(BugInfoFactory, "kritabuginfo.json", registerPlugin();) + +BugInfo::BugInfo(QObject *parent, const QVariantList &) + : KisViewPlugin(parent) +{ + KisAction *action = createAction("buginfo"); + action->setText(i18n("Show system information for bug reports.")); + connect(action, SIGNAL(triggered()), this, SLOT(slotBugInfo())); +} + + +BugInfo::~BugInfo() +{ +} + +void BugInfo::slotBugInfo() +{ + DlgBugInfo dlgBugInfo(m_view->mainWindow()); + dlgBugInfo.exec(); +} + +#include "buginfo.moc" diff --git a/plugins/extensions/buginfo/buginfo.h b/plugins/extensions/buginfo/buginfo.h new file mode 100644 index 0000000000..e6d031441c --- /dev/null +++ b/plugins/extensions/buginfo/buginfo.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef BUGINFO_H +#define BUGINFO_H + +#include +#include + +class KUndo2MagicString; + +class BugInfo : public KisViewPlugin +{ + Q_OBJECT +public: + BugInfo(QObject *parent, const QVariantList &); + ~BugInfo() override; + +public Q_SLOTS: + + void slotBugInfo(); + +}; + +#endif // BUGINFO_H diff --git a/plugins/extensions/buginfo/buginfo.xmlgui b/plugins/extensions/buginfo/buginfo.xmlgui new file mode 100644 index 0000000000..1002c9d910 --- /dev/null +++ b/plugins/extensions/buginfo/buginfo.xmlgui @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp new file mode 100644 index 0000000000..d1654f6c0b --- /dev/null +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017 Boudewijn Rempt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dlg_buginfo.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kis_document_aware_spin_box_unit_manager.h" + +DlgBugInfo::DlgBugInfo(QWidget *parent) + : KoDialog(parent) +{ + setCaption(i18n("Please paste this information in your bug report")); + + setButtons(User1 | Ok); + setButtonText(User1, i18n("Copy to clipboard")); + setDefaultButton(Ok); + + m_page = new WdgBugInfo(this); + Q_CHECK_PTR(m_page); + + setMainWidget(m_page); + + // OS information + QString info; + info.append("OS Information"); + info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); + info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); + info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); + info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); + info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); + info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); + info.append("\n Product Type: ").append(QSysInfo::productType()); + info.append("\n Product Version: ").append(QSysInfo::productVersion()); + info.append("\n"); + + // OpenGL information + // we need a QSurface active to get our GL functions from the context + QWindow surface; + surface.setSurfaceType( QSurface::OpenGLSurface ); + surface.create(); + + QOpenGLContext context; + context.create(); + if (!context.isValid()) return; + + context.makeCurrent( &surface ); + + QOpenGLFunctions *funcs = context.functions(); + funcs->initializeOpenGLFunctions(); + +#ifndef GL_RENDERER +# define GL_RENDERER 0x1F01 +#endif + QString Renderer = QString((const char*)funcs->glGetString(GL_RENDERER)); + info.append("\nOpenGL Info"); + info.append("\n Vendor: ").append(reinterpret_cast(funcs->glGetString(GL_VENDOR))); + info.append("\n Renderer: ").append(Renderer); + info.append("\n Version: ").append(reinterpret_cast(funcs->glGetString(GL_VERSION))); + info.append("\n Shading language: ").append(reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION))); + + int glMajorVersion = context.format().majorVersion(); + int glMinorVersion = context.format().minorVersion(); + bool supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); + + info.append(QString("\n Version: %1.%2").arg(glMajorVersion).arg(glMinorVersion)); + info.append(QString("\n Supports deprecated functions: %1").arg(supportsDeprecatedFunctions ? "true" : "false")); + + + + // Installation information + + // calculate a default height for the widget + int wheight = m_page->sizeHint().height(); + m_page->txtBugInfo->setText(info); + + QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); + int target_height = fm.height() * info.split('\n').size() + wheight; + + QDesktopWidget dw; + QRect screen_rect = dw.availableGeometry(dw.primaryScreen()); + + resize(sizeHint().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); + + connect(this, &KoDialog::user1Clicked, this, [this](){ QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); }); +} + +DlgBugInfo::~DlgBugInfo() +{ + delete m_page; +} diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/dlg_buginfo.h new file mode 100644 index 0000000000..619a737b05 --- /dev/null +++ b/plugins/extensions/buginfo/dlg_buginfo.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef DLG_BUGINFO +#define DLG_BUGINFO + +#include + +#include "ui_wdg_buginfo.h" + + +class WdgBugInfo : public QWidget, public Ui::WdgBugInfo +{ + Q_OBJECT + +public: + WdgBugInfo(QWidget *parent) : QWidget(parent) { + setupUi(this); + } +}; + +class DlgBugInfo: public KoDialog +{ + Q_OBJECT +public: + DlgBugInfo(QWidget * parent = 0); + ~DlgBugInfo() override; +private: + WdgBugInfo *m_page; +}; + +#endif // DLG_BUGINFO diff --git a/plugins/extensions/buginfo/kritabuginfo.json b/plugins/extensions/buginfo/kritabuginfo.json new file mode 100644 index 0000000000..c8deddf33d --- /dev/null +++ b/plugins/extensions/buginfo/kritabuginfo.json @@ -0,0 +1,9 @@ +{ + "Id": "Buginfo Plugin", + "Type": "Service", + "X-KDE-Library": "kritabuginfo", + "X-KDE-ServiceTypes": [ + "Krita/ViewPlugin" + ], + "X-Krita-Version": "28" +} diff --git a/plugins/extensions/buginfo/wdg_buginfo.ui b/plugins/extensions/buginfo/wdg_buginfo.ui new file mode 100644 index 0000000000..35c684948a --- /dev/null +++ b/plugins/extensions/buginfo/wdg_buginfo.ui @@ -0,0 +1,31 @@ + + + WdgBugInfo + + + + 0 + 0 + 367 + 439 + + + + Rotate Image + + + + + + Please add this information to a bug report: + + + + + + + + + + + diff --git a/plugins/extensions/imagesize/dlg_imagesize.cc b/plugins/extensions/imagesize/dlg_imagesize.cc index 846c7fa66b..65d48eaf16 100644 --- a/plugins/extensions/imagesize/dlg_imagesize.cc +++ b/plugins/extensions/imagesize/dlg_imagesize.cc @@ -1,422 +1,421 @@ /* * dlg_imagesize.cc - part of KimageShop^WKrayon^WKrita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2009 C. Boemann * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_imagesize.h" #include #include #include #include - #include #include "kis_double_parse_unit_spin_box.h" #include "kis_document_aware_spin_box_unit_manager.h" static const QString pixelStr(KoUnit::unitDescription(KoUnit::Pixel)); static const QString percentStr(i18n("Percent (%)")); static const QString pixelsInchStr(i18n("Pixels/Inch")); static const QString pixelsCentimeterStr(i18n("Pixels/Centimeter")); DlgImageSize::DlgImageSize(QWidget *parent, int width, int height, double resolution) : KoDialog(parent) , m_aspectRatio(((double) width) / height) , m_originalWidth(width) , m_originalHeight(height) , m_width(width) , m_height(height) , m_printWidth(width / resolution) , m_printHeight(height / resolution) , m_originalResolution(resolution) , m_resolution(resolution) , m_keepAspect(true) { setCaption(i18n("Scale To New Size")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgImageSize(this); Q_CHECK_PTR(m_page); m_page->layout()->setMargin(0); m_page->setObjectName("image_size"); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); //configure the unit to image length, default unit is pixel and printing units are forbiden. _widthUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH); _heightUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH); m_page->pixelWidthDouble->setUnitManager(_widthUnitManager); m_page->pixelHeightDouble->setUnitManager(_heightUnitManager); m_page->pixelWidthDouble->changeValue(width); m_page->pixelHeightDouble->changeValue(height); m_page->pixelWidthDouble->setDecimals(2); m_page->pixelHeightDouble->setDecimals(2); m_page->pixelWidthDouble->setDisplayUnit(false); m_page->pixelHeightDouble->setDisplayUnit(false); m_page->pixelWidthUnit->setModel(_widthUnitManager); m_page->pixelHeightUnit->setModel(_widthUnitManager); m_page->pixelWidthUnit->setCurrentText("px"); m_page->pixelHeightUnit->setCurrentText("px"); m_page->pixelFilterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); - m_page->pixelFilterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formatedDescriptions()); + m_page->pixelFilterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formattedDescriptions()); m_page->pixelFilterCmb->setCurrent("Bicubic"); _printWidthUnitManager = new KisSpinBoxUnitManager(this); _printHeightUnitManager = new KisSpinBoxUnitManager(this); m_page->printWidth->setUnitManager(_printWidthUnitManager); m_page->printHeight->setUnitManager(_printHeightUnitManager); m_page->printWidth->setDecimals(2); m_page->printHeight->setDecimals(2); m_page->printWidth->setDisplayUnit(false); m_page->printHeight->setDisplayUnit(false); m_page->printResolution->setDecimals(2); m_page->printResolution->setAlignment(Qt::AlignRight); m_page->printWidthUnit->setModel(_printWidthUnitManager); m_page->printHeightUnit->setModel(_printHeightUnitManager); m_page->printWidth->changeValue(m_printWidth); m_page->printHeight->changeValue(m_printHeight); //TODO: create a resolution dimension in the unit manager. m_page->printResolutionUnit->addItem(pixelsInchStr); m_page->printResolutionUnit->addItem(pixelsCentimeterStr); m_page->pixelAspectRatioBtn->setKeepAspectRatio(true); m_page->printAspectRatioBtn->setKeepAspectRatio(true); m_page->constrainProportionsCkb->setChecked(true); KisSizeGroup *labelsGroup = new KisSizeGroup(this); labelsGroup->addWidget(m_page->lblPixelWidth); labelsGroup->addWidget(m_page->lblPixelHeight); labelsGroup->addWidget(m_page->lblPixelFilter); labelsGroup->addWidget(m_page->lblPrintWidth); labelsGroup->addWidget(m_page->lblPrintHeight); labelsGroup->addWidget(m_page->lblResolution); KisSizeGroup *spinboxesGroup = new KisSizeGroup(this); spinboxesGroup->addWidget(m_page->pixelWidthDouble); spinboxesGroup->addWidget(m_page->pixelHeightDouble); spinboxesGroup->addWidget(m_page->printWidth); spinboxesGroup->addWidget(m_page->printHeight); spinboxesGroup->addWidget(m_page->printResolution); KisSizeGroup *comboboxesGroup = new KisSizeGroup(this); comboboxesGroup->addWidget(m_page->pixelWidthUnit); comboboxesGroup->addWidget(m_page->pixelHeightUnit); comboboxesGroup->addWidget(m_page->printWidthUnit); comboboxesGroup->addWidget(m_page->printHeightUnit); comboboxesGroup->addWidget(m_page->printResolutionUnit); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); connect(m_page->pixelAspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->printAspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->pixelWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotPixelWidthChanged(double))); connect(m_page->pixelHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotPixelHeightChanged(double))); connect(m_page->pixelWidthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->pixelHeightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->pixelWidthUnit, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->pixelHeightUnit, SLOT(setCurrentIndex(int))); connect(m_page->printWidth, SIGNAL(valueChangedPt(double)), this, SLOT(slotPrintWidthChanged(double))); connect(m_page->printHeight, SIGNAL(valueChangedPt(double)), this, SLOT(slotPrintHeightChanged(double))); connect(m_page->printWidthUnit, SIGNAL(currentIndexChanged(int)), _printWidthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->printHeightUnit, SIGNAL(currentIndexChanged(int)), _printHeightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_printWidthUnitManager, SIGNAL(unitChanged(int)), m_page->printWidthUnit, SLOT(setCurrentIndex(int))); connect(_printHeightUnitManager, SIGNAL(unitChanged(int)), m_page->printHeightUnit, SLOT(setCurrentIndex(int))); connect(m_page->printResolution, SIGNAL(valueChanged(double)), this, SLOT(slotPrintResolutionChanged(double))); connect(m_page->printResolution, SIGNAL(editingFinished()), this, SLOT(slotPrintResolutionEditFinished())); connect(m_page->printResolutionUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPrintResolutionUnitChanged())); // pick selected print units from user locale (after slots connection, so the spinbox will be updated too). if (QLocale().measurementSystem() == QLocale::MetricSystem) { m_page->printWidthUnit->setCurrentText("cm"); m_page->printHeightUnit->setCurrentText("cm"); m_page->printResolutionUnit->setCurrentIndex(0); // Pixels/Centimeter slotPrintResolutionUnitChanged(); //ensure the resolution is updated, even if the index didn't changed. } else { // Imperial m_page->printWidthUnit->setCurrentText("in"); m_page->printHeightUnit->setCurrentText("in"); m_page->printResolutionUnit->setCurrentIndex(1); // Pixels/Inch slotPrintResolutionUnitChanged(); //ensure the resolution is updated, even if the index didn't changed. } setMainWidget(m_page); } DlgImageSize::~DlgImageSize() { delete m_page; } qint32 DlgImageSize::width() { return (qint32)m_width; } qint32 DlgImageSize::height() { return (qint32)m_height; } double DlgImageSize::resolution() { return m_resolution; } KisFilterStrategy *DlgImageSize::filterType() { KoID filterID = m_page->pixelFilterCmb->currentItem(); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id()); return filter; } // SLOTS void DlgImageSize::slotPixelWidthChanged(double w) { m_width = w; m_printWidth = m_width / m_resolution; updatePrintWidthUIValue(m_printWidth); if (m_keepAspect) { m_height = qRound(m_width / m_aspectRatio); updatePixelHeightUIValue(m_height); m_printHeight = m_height / m_resolution; updatePrintHeightUIValue(m_printHeight); } } void DlgImageSize::slotPixelHeightChanged(double h) { m_height = h; m_printHeight = m_height / m_resolution; updatePrintHeightUIValue(m_printHeight); if (m_keepAspect) { m_width = qRound(m_height * m_aspectRatio); updatePixelWidthUIValue(m_width); m_printWidth = m_width / m_resolution; updatePrintWidthUIValue(m_printWidth); } } void DlgImageSize::slotPrintWidthChanged(double w) { m_printWidth = w; if (m_keepAspect) { m_printHeight = m_printWidth / m_aspectRatio; updatePrintHeightUIValue(m_printHeight); } if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { m_resolution = m_width / m_printWidth; updatePrintResolutionUIValue(m_resolution); if (!m_keepAspect) { // compute and update a new image height value from the print size values const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_height = qRound(printHeightInch * 72 * m_resolution); updatePixelHeightUIValue(m_height); } } else { const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_width = qRound(printWidthInch * 72 * m_resolution); updatePixelWidthUIValue(m_width); if (m_keepAspect) { m_height = qRound(m_width / m_aspectRatio); updatePixelHeightUIValue(m_height); } } } void DlgImageSize::slotPrintHeightChanged(double h) { m_printHeight = h; if (m_keepAspect) { m_printWidth = m_printHeight * m_aspectRatio; updatePrintWidthUIValue(m_printWidth); } if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { m_resolution = m_height / m_printHeight; updatePrintResolutionUIValue(m_resolution); if (!m_keepAspect) { // compute and update a new image width value from the print size values const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_width = qRound(printWidthInch * 72 * m_resolution); updatePixelWidthUIValue(m_width); } } else { const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); m_height = qRound(printHeightInch * 72 * m_resolution); updatePixelHeightUIValue(m_height); if (m_keepAspect) { m_width = qRound(m_height * m_aspectRatio); updatePixelWidthUIValue(m_width); } } } void DlgImageSize::slotAspectChanged(bool keep) { m_page->pixelAspectRatioBtn->blockSignals(true); m_page->printAspectRatioBtn->blockSignals(true); m_page->constrainProportionsCkb->blockSignals(true); m_page->pixelAspectRatioBtn->setKeepAspectRatio(keep); m_page->printAspectRatioBtn->setKeepAspectRatio(keep); m_page->constrainProportionsCkb->setChecked(keep); m_page->pixelAspectRatioBtn->blockSignals(false); m_page->printAspectRatioBtn->blockSignals(false); m_page->constrainProportionsCkb->blockSignals(false); m_keepAspect = keep; if (keep) { // values may be out of sync, so we need to reset it to defaults m_width = m_originalWidth; m_height = m_originalHeight; m_printWidth = m_originalWidth / m_originalResolution; m_printHeight = m_originalHeight / m_originalResolution; m_resolution = m_originalResolution; updatePixelWidthUIValue(m_width); updatePixelHeightUIValue(m_height); updatePrintWidthUIValue(m_printWidth); updatePrintHeightUIValue(m_printHeight); updatePrintResolutionUIValue(m_resolution); } } void DlgImageSize::slotPrintResolutionChanged(double r) { if (m_page->printResolutionUnit->currentText() == pixelsInchStr) m_resolution = KoUnit::convertFromUnitToUnit(r, KoUnit(KoUnit::Pixel), KoUnit(KoUnit::Inch)); else m_resolution = KoUnit::convertFromUnitToUnit(r, KoUnit(KoUnit::Pixel), KoUnit(KoUnit::Centimeter)); if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { m_printWidth = m_width / m_resolution; m_printHeight = m_height / m_resolution; updatePrintWidthUIValue(m_printWidth); updatePrintHeightUIValue(m_printHeight); } else { // Do not commit m_width and m_height values yet. This is done to avoid // nasty results in image size values while the user is typing a resolution value const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); const int width = qRound(printWidthInch * 72 * m_resolution); const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); const int height = qRound(printHeightInch * 72 * m_resolution); updatePixelWidthUIValue(width); updatePixelHeightUIValue(height); } } void DlgImageSize::slotPrintResolutionEditFinished() { if (!m_page->adjustPrintSizeSeparatelyCkb->isChecked()) { const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); // Commit width and height values m_width = qRound(printWidthInch * 72 * m_resolution); m_height = qRound(printHeightInch * 72 * m_resolution); // Note that spinbox values should be up to date // (updated through slotResolutionChanged()) } } void DlgImageSize::slotPrintResolutionUnitChanged() { updatePrintResolutionUIValue(m_resolution); } void DlgImageSize::updatePixelWidthUIValue(double value) { m_page->pixelWidthDouble->blockSignals(true); m_page->pixelWidthDouble->changeValue(value); m_page->pixelWidthDouble->blockSignals(false); } void DlgImageSize::updatePixelHeightUIValue(double value) { m_page->pixelHeightDouble->blockSignals(true); m_page->pixelHeightDouble->changeValue(value); m_page->pixelHeightDouble->blockSignals(false); } void DlgImageSize::updatePrintWidthUIValue(double value) { m_page->printWidth->blockSignals(true); m_page->printWidth->changeValue(value); m_page->printWidth->blockSignals(false); } void DlgImageSize::updatePrintHeightUIValue(double value) { m_page->printHeight->blockSignals(true); m_page->printHeight->changeValue(value); m_page->printHeight->blockSignals(false); } void DlgImageSize::updatePrintResolutionUIValue(double value) { double uiValue = 0.0; if (m_page->printResolutionUnit->currentText() == pixelsInchStr) { // show the value in pixel/inch unit uiValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel)); } else { // show the value in pixel/centimeter unit uiValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Centimeter), KoUnit(KoUnit::Pixel)); } m_page->printResolution->blockSignals(true); m_page->printResolution->setValue(uiValue); m_page->printResolution->blockSignals(false); } diff --git a/plugins/extensions/imagesize/dlg_layersize.cc b/plugins/extensions/imagesize/dlg_layersize.cc index 621eaf24e3..290b9f829f 100644 --- a/plugins/extensions/imagesize/dlg_layersize.cc +++ b/plugins/extensions/imagesize/dlg_layersize.cc @@ -1,197 +1,197 @@ /* * dlg_layersize.cc - part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Sven Langkamp * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_layersize.h" #include #include #include #include // XXX: I'm really real bad at arithmetic, let alone math. Here // be rounding errors. (Boudewijn) static const QString pixelStr(KoUnit::unitDescription(KoUnit::Pixel)); static const QString percentStr(i18n("Percent (%)")); DlgLayerSize::DlgLayerSize(QWidget * parent, const char * name, int width, int height, double resolution) : KoDialog(parent) , m_aspectRatio(((double) width) / height) , m_originalWidth(width) , m_originalHeight(height) , m_width(width) , m_height(height) , m_resolution(resolution) , m_keepAspect(true) { setCaption(i18n("Layer Size")); setObjectName(name); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgLayerSize(this); Q_CHECK_PTR(m_page); m_page->layout()->setMargin(0); m_page->setObjectName(name); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); _widthUnitManager->setApparentUnitFromSymbol("px"); _heightUnitManager->setApparentUnitFromSymbol("px"); m_page->newWidthDouble->setUnitManager(_widthUnitManager); m_page->newHeightDouble->setUnitManager(_heightUnitManager); m_page->newWidthDouble->setDecimals(2); m_page->newHeightDouble->setDecimals(2); m_page->newWidthDouble->setDisplayUnit(false); m_page->newHeightDouble->setDisplayUnit(false); m_page->newWidthDouble->setValue(width); m_page->newWidthDouble->setFocus(); m_page->newHeightDouble->setValue(height); m_page->filterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); - m_page->filterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formatedDescriptions()); + m_page->filterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formattedDescriptions()); m_page->filterCmb->setCurrent("Bicubic"); m_page->newWidthUnit->setModel(_widthUnitManager); m_page->newHeightUnit->setModel(_heightUnitManager); const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units. m_page->newWidthUnit->setCurrentIndex(pixelUnitIndex); m_page->newHeightUnit->setCurrentIndex(pixelUnitIndex); m_page->aspectRatioBtn->setKeepAspectRatio(true); m_page->constrainProportionsCkb->setChecked(true); setMainWidget(m_page); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); connect(m_page->aspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->newWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotWidthChanged(double))); connect(m_page->newHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotHeightChanged(double))); connect(m_page->newWidthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->newHeightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->newWidthUnit, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->newHeightUnit, SLOT(setCurrentIndex(int))); } DlgLayerSize::~DlgLayerSize() { delete m_page; } qint32 DlgLayerSize::width() { return (qint32)m_width; } qint32 DlgLayerSize::height() { return (qint32)m_height; } KisFilterStrategy *DlgLayerSize::filterType() { KoID filterID = m_page->filterCmb->currentItem(); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id()); return filter; } // SLOTS void DlgLayerSize::slotWidthChanged(double w) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = w*_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_width = qRound(resValue); if (m_keepAspect) { m_height = qRound(m_width / m_aspectRatio); m_page->newHeightDouble->blockSignals(true); m_page->newHeightDouble->changeValue(w / m_aspectRatio); m_page->newHeightDouble->blockSignals(false); } } void DlgLayerSize::slotHeightChanged(double h) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = h*_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_height = qRound(resValue); if (m_keepAspect) { m_width = qRound(m_height * m_aspectRatio); m_page->newWidthDouble->blockSignals(true); m_page->newWidthDouble->changeValue(h * m_aspectRatio); m_page->newWidthDouble->blockSignals(false); } } void DlgLayerSize::slotAspectChanged(bool keep) { m_page->aspectRatioBtn->blockSignals(true); m_page->constrainProportionsCkb->blockSignals(true); m_page->aspectRatioBtn->setKeepAspectRatio(keep); m_page->constrainProportionsCkb->setChecked(keep); m_page->aspectRatioBtn->blockSignals(false); m_page->constrainProportionsCkb->blockSignals(false); m_keepAspect = keep; if (keep) { // values may be out of sync, so we need to reset it to defaults m_width = m_originalWidth; m_height = m_originalHeight; updateWidthUIValue(m_width); updateHeightUIValue(m_height); } } void DlgLayerSize::updateWidthUIValue(double value) { m_page->newWidthDouble->blockSignals(true); const double resValue = value/_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->newWidthDouble->changeValue(resValue); m_page->newWidthDouble->blockSignals(false); } void DlgLayerSize::updateHeightUIValue(double value) { m_page->newHeightDouble->blockSignals(true); const double resValue = value/_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->newHeightDouble->changeValue(resValue); m_page->newHeightDouble->blockSignals(false); } diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip index 36d3b80539..a96ce283a8 100644 --- a/plugins/extensions/pykrita/sip/krita/Document.sip +++ b/plugins/extensions/pykrita/sip/krita/Document.sip @@ -1,62 +1,65 @@ class Document : QObject /NoDefaultCtors/ { %TypeHeaderCode #include "Document.h" %End Document(const Document & __0); public: bool operator==(const Document &other) const; bool operator!=(const Document &other) const; public Q_SLOTS: Node * activeNode() const /Factory/; void setActiveNode(Node* value); QList topLevelNodes() const /Factory/; Node *nodeByName(const QString &node) const /Factory/; bool batchmode() const; void setBatchmode(bool value); QString colorDepth() const; QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &value, const QString &colorDepth, const QString &colorProfile); QString documentInfo() const; void setDocumentInfo(const QString &document); QString fileName() const; void setFileName(QString value); int height() const; void setHeight(int value); QString name() const; void setName(QString value); int resolution() const; void setResolution(int value); Node * rootNode() const /Factory/; Selection * selection() const /Factory/; void setSelection(Selection* value); int width() const; void setWidth(int value); double xRes() const; void setXRes(double xRes) const; double yRes() const; void setYRes(double yRes) const; QByteArray pixelData(int x, int y, int w, int h) const; bool close(); void crop(int x, int y, int w, int h); bool exportImage(const QString &filename, const InfoObject & exportConfiguration); void flatten(); void resizeImage(int w, int h); + void scaleImage(int w, int h, int xres, int yres, QString strategy); + void rotateImage(double radians); + void shearImage(double angleX, double angleY); bool save(); bool saveAs(const QString & filename); Node *createNode(const QString & name, const QString & nodeType) /Factory/; QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; QImage thumbnail(int w, int h) const; void lock(); void unlock(); void waitForDone(); bool tryBarrierLock(); bool isIdle(); void refreshProjection(); private: }; diff --git a/plugins/extensions/pykrita/sip/krita/Krita.sip b/plugins/extensions/pykrita/sip/krita/Krita.sip index 07677e84ec..98d2cb8d49 100644 --- a/plugins/extensions/pykrita/sip/krita/Krita.sip +++ b/plugins/extensions/pykrita/sip/krita/Krita.sip @@ -1,57 +1,58 @@ class Krita : QObject { %TypeHeaderCode #include "Krita.h" %End public: public Q_SLOTS: Krita(QObject* parent /TransferThis/ = 0); virtual ~Krita(); Document * activeDocument() const /Factory/; void setActiveDocument(Document* value); bool batchmode() const; void setBatchmode(bool value); QList actions() const /Factory/; Action * action(const QString & name) const; QList documents() const /Factory/; QStringList filters() const; Filter * filter(const QString &name) const /Factory/; + QStringList filterStrategies() const; QStringList profiles(const QString &colorModel, const QString &ColorDepth) const; bool addProfile(const QString &profilePath); Notifier * notifier() const; QString version() const; QList views() const /Factory/; Window * activeWindow() const /Factory/; QList windows() const /Factory/; QMap resources(const QString &type) const /Factory/; Document * createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile) /Factory/; Document * openDocument(const QString &filename) /Factory/; Window * openWindow(); Action * createAction(const QString & text); void addExtension(Extension* _extension /GetWrapper/); %MethodCode Py_BEGIN_ALLOW_THREADS sipCpp->addExtension(a0); Py_END_ALLOW_THREADS sipTransferTo(a0Wrapper, Py_None); %End void addDockWidgetFactory(DockWidgetFactoryBase* _factory /GetWrapper/); %MethodCode Py_BEGIN_ALLOW_THREADS sipCpp->addDockWidgetFactory(a0); Py_END_ALLOW_THREADS sipTransferTo(a0Wrapper, Py_None); %End void writeSetting(const QString &group, const QString &name, const QString &value); QString readSetting(const QString &group, const QString &name, const QString &defaultValue); static Krita * instance(); static QObject * fromVariant(const QVariant & v); private: Krita(const Krita &); // Generated }; diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip index e4c7380d36..1e59d02353 100644 --- a/plugins/extensions/pykrita/sip/krita/Node.sip +++ b/plugins/extensions/pykrita/sip/krita/Node.sip @@ -1,57 +1,62 @@ class Node : QObject { %TypeHeaderCode #include "Node.h" %End Node(const Node & __0); public: virtual ~Node(); bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: bool alphaLocked() const; void setAlphaLocked(bool value); QString blendingMode() const; void setBlendingMode(QString value); QList channels() const; QList childNodes() const; bool addChildNode(Node *child, Node *above); bool removeChildNode(Node *child); void setChildNodes(QList nodes); QString colorDepth() const; bool animated() const; void enableAnimation() const; void setCollapsed(bool collapsed); bool collapsed() const; int colorLabel() const; void setColorLabel(int value); QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); bool inheritAlpha() const; void setInheritAlpha(bool value); bool locked() const; void setLocked(bool value); QString name() const; void setName(QString value); int opacity() const; void setOpacity(int value); Node * parentNode() const /Factory/; QString type() const; bool visible() const; void setVisible(bool value); QByteArray pixelData(int x, int y, int w, int h) const; + QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; QByteArray projectionPixelData(int x, int y, int w, int h) const; void setPixelData(QByteArray value, int x, int y, int w, int h); QRect bounds() const; void move(int x, int y); QPoint position() const; bool remove(); Node *duplicate() /Factory/; void save(const QString &filename, double xRes, double yRes); Node *mergeDown() /Factory/; + void scaleNode(int width, int height, QString strategy); + void rotateNode(double radians); + void cropNode(int x, int y, int w, int h); + void shearNode(double angleX, double angleY); QImage thumbnail(int w, int h); Q_SIGNALS: private: }; diff --git a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui index 551e277883..f9eaa7556b 100644 --- a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui +++ b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui @@ -1,238 +1,247 @@ WdgHSVAdjustment 0 0 - 406 + 412 188 0 0 0 0 5 Qt::Vertical 20 0 &Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter cmbType &Colorize 0 Hue/Saturation/Value Hue/Saturation/Lightness Hue/Saturation/Intensity Hue/Saturation/Luma Blue Chroma/Red Chroma/Luma -180 180 Qt::Horizontal -100 100 - Value: + &Value: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + valueSlider + -100 100 0 true Qt::Horizontal false false QSlider::NoTicks 0 0 &Saturation: false Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + saturationSlider + 0 0 &Hue: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + hueSlider + -100 100 Qt::Horizontal -100 100 -180 180 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
cmbType chkColorize
diff --git a/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h b/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h index 2d34d2ea70..82889a3666 100644 --- a/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h +++ b/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h @@ -1,129 +1,129 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGTEXTHELPER_H #define SVGTEXTHELPER_H #include #include #include #include typedef QList CharTransforms; -class KoXmlElement; +#include class SvgGraphicsContext; class ArtisticTextLoadingContext { public: enum OffsetType { None, Absolute, Relative }; ArtisticTextLoadingContext(); static QString simplifyText(const QString &text, bool preserveWhiteSpace = false); /// Parses current character transforms (x,y,dx,dy,rotate) void parseCharacterTransforms(const KoXmlElement &element, SvgGraphicsContext *gc); /// Pushes the current character transforms to the stack void pushCharacterTransforms(); /// Pops last character transforms from the stack void popCharacterTransforms(); /// Checks current x-offset type OffsetType xOffsetType() const; /// Checks current y-offset type OffsetType yOffsetType() const; /// Returns x-offsets from stack CharTransforms xOffsets(int count); /// Returns y-offsets from stack CharTransforms yOffsets(int count); /// Returns rotations from stack CharTransforms rotations(int count); /// Returns the text position QPointF textPosition() const; private: void printDebug(); struct CharTransformState { CharTransformState() : hasData(false), lastTransform(0.0) { } CharTransformState(const CharTransforms &initialData) : data(initialData), hasData(!initialData.isEmpty()) , lastTransform(initialData.isEmpty() ? 0.0 : initialData.last()) { } CharTransforms extract(int count) { const int copyCount = qMin(data.count(), count); CharTransforms extracted = data.mid(0, copyCount); data = data.mid(copyCount); return extracted; } CharTransforms data; bool hasData; qreal lastTransform; }; typedef QList CharTransformStack; enum ValueType { Number, XLength, YLength }; /// Parses offset values from the given string CharTransforms parseList(const QString &listString, SvgGraphicsContext *gc, ValueType type); /// Collects number of specified transforms values from the stack CharTransforms collectValues(int count, CharTransformState ¤t, CharTransformStack &stack); CharTransformState m_currentAbsolutePosX; ///< current absolute character x-positions CharTransformState m_currentAbsolutePosY; ///< current absolute character y-positions CharTransformState m_currentRelativePosX; ///< current relative character x-positions CharTransformState m_currentRelativePosY; ///< current relative character y-positions CharTransformState m_currentRotations; ///< current character rotations CharTransformStack m_absolutePosX; ///< stack of absolute character x-positions CharTransformStack m_absolutePosY; ///< stack of absolute character y-positions CharTransformStack m_relativePosX; ///< stack of relative character x-positions CharTransformStack m_relativePosY; ///< stack of relative character y-positions CharTransformStack m_rotations; ///< stack of character rotations QPointF m_textPosition; }; #endif // SVGTEXTHELPER_H diff --git a/plugins/flake/textshape/kotext/KoSection.h b/plugins/flake/textshape/kotext/KoSection.h index 7d4e29e9ea..12dd9c2ded 100644 --- a/plugins/flake/textshape/kotext/KoSection.h +++ b/plugins/flake/textshape/kotext/KoSection.h @@ -1,135 +1,135 @@ /* * Copyright (c) 2011 Boudewijn Rempt * Copyright (c) 2014-2015 Denis Kuplyakov * * 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 KOSECTION_H #define KOSECTION_H #include "kritatext_export.h" #include #include #include #include #include #include -class KoXmlElement; +#include class KoShapeSavingContext; class KoTextSharedLoadingData; class KoSectionEnd; class KoElementReference; class KoTextInlineRdf; class KoSectionPrivate; /** * Contains the information about the current text:section. * * The element has the following attributes: * *
    *
  • text:condition *
  • text:display *
  • text:name *
  • text:protected *
  • text:protection-key *
  • text:protection-key-digest-algorithm *
  • text:style-name *
  • xml:id *
* (odf spec v.12) */ class KRITATEXT_EXPORT KoSection { public: ~KoSection(); /// Returns section name QString name() const; /// Returns starting and ending position of section in QTextDocument QPair bounds() const; /// Returns section level. Root section has @c 0 level. int level() const; /** Returns inlineRdf associated with section * @return pointer to the KoTextInlineRdf for this section */ KoTextInlineRdf *inlineRdf() const; /** Sets KoTextInlineRdf for this section * @param inlineRdf pointer to KoTextInlineRdf to set */ void setInlineRdf(KoTextInlineRdf *inlineRdf); bool loadOdf(const KoXmlElement &element, KoTextSharedLoadingData *sharedData, bool stylesDotXml); void saveOdf(KoShapeSavingContext &context) const; protected: const QScopedPointer d_ptr; private: Q_DISABLE_COPY(KoSection) Q_DECLARE_PRIVATE(KoSection) explicit KoSection(const QTextCursor &cursor, const QString &name, KoSection *parent); /// Changes section's name to @param name void setName(const QString &name); /// Sets paired KoSectionsEnd for this section. void setSectionEnd(KoSectionEnd *sectionEnd); /** * Sets level of section in section tree. * Root sections have @c 0 level. */ void setLevel(int level); /// Returns a pointer to the parent of the section in tree. KoSection *parent() const; /// Returns a vector of pointers to the children of the section. QVector children() const; /** * Specifies if end bound of section should stay on place when inserting text. * Used by KoTextLoader on document loading. * @see QTextCursor::setKeepPositionOnInsert(bool) */ void setKeepEndBound(bool state); /** * Inserts @param section to position @param childIdx of children */ void insertChild(int childIdx, KoSection *section); /** * Removes child on position @param childIdx */ void removeChild(int childIdx); friend class KoSectionModel; friend class KoTextLoader; // accesses setKeepEndBound() function friend class KoSectionEnd; friend class TestKoTextEditor; // accesses setKeepEndBound() function }; Q_DECLARE_METATYPE(KoSection *) Q_DECLARE_METATYPE(QList) #endif // KOSECTION_H diff --git a/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp b/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp index 1a6658c4ad..de796e7423 100644 --- a/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp +++ b/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp @@ -1,739 +1,668 @@ /* This file is part of the KDE project * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoChangeTracker.h" //Calligra includes #include "styles/KoCharacterStyle.h" #include "KoChangeTrackerElement.h" #include #include #include #include #include #include #include #include #include "KoFormatChangeInformation.h" #include //KDE includes #include "TextDebug.h" #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoChangeTracker::Private { public: Private() : changeId(1), recordChanges(false), displayChanges(false), insertionBgColor(101,255,137), deletionBgColor(255,185,185), formatChangeBgColor(195,195,255), changeSaveFormat(UNKNOWN) { } ~Private() { } QMultiHash children; QMultiHash duplicateIds; QHash parents; QHash changes; QHash loadedChanges; QHash changeInformation; QList saveChanges; QList acceptedRejectedChanges; int changeId; bool recordChanges; bool displayChanges; QColor insertionBgColor, deletionBgColor, formatChangeBgColor; QString changeAuthorName; KoChangeTracker::ChangeSaveFormat changeSaveFormat; }; KoChangeTracker::KoChangeTracker(QObject *parent) : QObject(parent), d(new Private()) { d->changeId = 1; } KoChangeTracker::~KoChangeTracker() { delete d; } void KoChangeTracker::setRecordChanges(bool enabled) { d->recordChanges = enabled; } bool KoChangeTracker::recordChanges() const { return d->recordChanges; } void KoChangeTracker::setDisplayChanges(bool enabled) { d->displayChanges = enabled; } bool KoChangeTracker::displayChanges() const { return d->displayChanges; } QString KoChangeTracker::authorName() const { return d->changeAuthorName; } void KoChangeTracker::setAuthorName(const QString &authorName) { d->changeAuthorName = authorName; } KoChangeTracker::ChangeSaveFormat KoChangeTracker::saveFormat() const { return d->changeSaveFormat; } void KoChangeTracker::setSaveFormat(ChangeSaveFormat saveFormat) { d->changeSaveFormat = saveFormat; } int KoChangeTracker::getFormatChangeId(const KUndo2MagicString &title, const QTextFormat &format, const QTextFormat &prevFormat, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::FormatChange); changeElement->setChangeFormat(format); changeElement->setPrevFormat(prevFormat); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getInsertChangeId(const KUndo2MagicString &title, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::InsertChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getDeleteChangeId(const KUndo2MagicString &title, const QTextDocumentFragment &selection, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::DeleteChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setDeleteData(selection); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } KoChangeTrackerElement* KoChangeTracker::elementById(int id) const { if (isDuplicateChangeId(id)) { id = originalChangeId(id); } return d->changes.value(id); } bool KoChangeTracker::removeById(int id, bool freeMemory) { if (freeMemory) { KoChangeTrackerElement *temp = d->changes.value(id); delete temp; } return d->changes.remove(id); } bool KoChangeTracker::containsInlineChanges(const QTextFormat &format) const { if (format.property(KoCharacterStyle::ChangeTrackerId).toInt()) return true; return false; } int KoChangeTracker::mergeableId(KoGenChange::Type type, const KUndo2MagicString &title, int existingId) const { if (!existingId || !d->changes.value(existingId)) return 0; if (d->changes.value(existingId)->getChangeType() == type && d->changes.value(existingId)->getChangeTitle() == title) { return existingId; } else { if (d->parents.contains(existingId)) { return mergeableId(type, title, d->parents.value(existingId)); } else { return 0; } } } int KoChangeTracker::split(int changeId) { KoChangeTrackerElement *element = new KoChangeTrackerElement(*d->changes.value(changeId)); d->changes.insert(d->changeId, element); return d->changeId++; } bool KoChangeTracker::isParent(int testedParentId, int testedChildId) const { if ((testedParentId == testedChildId) && !d->acceptedRejectedChanges.contains(testedParentId)) return true; else if (d->parents.contains(testedChildId)) return isParent(testedParentId, d->parents.value(testedChildId)); else return false; } void KoChangeTracker::setParent(int child, int parent) { if (!d->children.values(parent).contains(child)) { d->children.insert(parent, child); } if (!d->parents.contains(child)) { d->parents.insert(child, parent); } } int KoChangeTracker::parent(int changeId) const { if (!d->parents.contains(changeId)) return 0; if (d->acceptedRejectedChanges.contains(d->parents.value(changeId))) return parent(d->parents.value(changeId)); return d->parents.value(changeId); } int KoChangeTracker::createDuplicateChangeId(int existingChangeId) { int duplicateChangeId = d->changeId; d->changeId++; d->duplicateIds.insert(existingChangeId, duplicateChangeId); return duplicateChangeId; } bool KoChangeTracker::isDuplicateChangeId(int duplicateChangeId) const { return d->duplicateIds.values().contains(duplicateChangeId); } int KoChangeTracker::originalChangeId(int duplicateChangeId) const { int originalChangeId = 0; QMultiHash::const_iterator i = d->duplicateIds.constBegin(); while (i != d->duplicateIds.constEnd()) { if (duplicateChangeId == i.value()) { originalChangeId = i.key(); break; } ++i; } return originalChangeId; } void KoChangeTracker::acceptRejectChange(int changeId, bool set) { if (set) { if (!d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.append(changeId); } else { if (d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.removeAll(changeId); } d->changes.value(changeId)->setAcceptedRejected(set); } bool KoChangeTracker::saveInlineChange(int changeId, KoGenChange &change) { if (!d->changes.contains(changeId)) return false; change.setType(d->changes.value(changeId)->getChangeType()); change.addChangeMetaData("dc-creator", d->changes.value(changeId)->getCreator()); change.addChangeMetaData("dc-date", d->changes.value(changeId)->getDate()); if (d->changes.value(changeId)->hasExtraMetaData()) change.addChildElement("changeMetaData", d->changes.value(changeId)->getExtraMetaData()); return true; } QMap KoChangeTracker::saveInlineChanges(QMap changeTransTable, KoGenChanges &genChanges) { foreach (int changeId, d->changes.keys()) { // return if the id we find in the changetranstable already has a length. if (changeTransTable.value(changeId).length()) { continue; } if ((elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) && (saveFormat() == KoChangeTracker::ODF_1_2)) { continue; } KoGenChange change; if (saveFormat() == KoChangeTracker::ODF_1_2) { change.setChangeFormat(KoGenChange::ODF_1_2); } else { change.setChangeFormat(KoGenChange::DELTAXML); } saveInlineChange(changeId, change); QString changeName = genChanges.insert(change); changeTransTable.insert(changeId, changeName); } return changeTransTable; } void KoChangeTracker::setFormatChangeInformation(int formatChangeId, KoFormatChangeInformation *formatInformation) { d->changeInformation.insert(formatChangeId, formatInformation); } KoFormatChangeInformation *KoChangeTracker::formatChangeInformation(int formatChangeId) const { return d->changeInformation.value(formatChangeId); } -void KoChangeTracker::loadOdfChanges(const KoXmlElement& element) -{ - if (element.namespaceURI() == KoXmlNS::text) { - KoXmlElement tag; - forEachElement(tag, element) { - if (! tag.isNull()) { - const QString localName = tag.localName(); - if (localName == "changed-region") { - KoChangeTrackerElement *changeElement = 0; - KoXmlElement region; - forEachElement(region, tag) { - if (!region.isNull()) { - if (region.localName() == "insertion") { - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::InsertChange); - } else if (region.localName() == "format-change") { - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::FormatChange); - } else if (region.localName() == "deletion") { - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::DeleteChange); - } - KoXmlElement metadata = region.namedItemNS(KoXmlNS::office,"change-info").toElement(); - if (!metadata.isNull()) { - KoXmlElement date = metadata.namedItem("dc:date").toElement(); - if (!date.isNull()) { - changeElement->setDate(date.text()); - } - KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); - if (!date.isNull()) { - changeElement->setCreator(creator.text()); - } - //TODO load comments -/* KoXmlElement extra = metadata.namedItem("dc-").toElement(); - if (!date.isNull()) { - debugText << "creator: " << creator.text(); - changeElement->setCreator(creator.text()); - }*/ - } - changeElement->setEnabled(d->recordChanges); - d->changes.insert( d->changeId, changeElement); - d->loadedChanges.insert(tag.attributeNS(KoXmlNS::text,"id"), d->changeId++); - } - } - } - } - } - } else { - //This is the ODF 1.2 Change Format - KoXmlElement tag; - forEachElement(tag, element) { - if (! tag.isNull()) { - const QString localName = tag.localName(); - if (localName == "change-transaction") { - KoChangeTrackerElement *changeElement = 0; - //Set the change element as an insertion element for now - //Will be changed to the correct type when actual changes referencing this change-id are encountered - changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::delta,"change-id")),KoGenChange::InsertChange); - KoXmlElement metadata = tag.namedItemNS(KoXmlNS::delta,"change-info").toElement(); - if (!metadata.isNull()) { - KoXmlElement date = metadata.namedItem("dc:date").toElement(); - if (!date.isNull()) { - changeElement->setDate(date.text()); - } - KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); - if (!creator.isNull()) { - changeElement->setCreator(creator.text()); - } - } - changeElement->setEnabled(d->recordChanges); - d->changes.insert( d->changeId, changeElement); - d->loadedChanges.insert(tag.attributeNS(KoXmlNS::delta,"change-id"), d->changeId++); - } - } - } - } +void KoChangeTracker::loadOdfChanges(const KoXmlElement& ) +{ } int KoChangeTracker::getLoadedChangeId(const QString &odfId) const { return d->loadedChanges.value(odfId); } int KoChangeTracker::getDeletedChanges(QVector& deleteVector) const { int numAppendedItems = 0; foreach (KoChangeTrackerElement *element, d->changes.values()) { if(element->getChangeType() == KoGenChange::DeleteChange && !element->acceptedRejected()) { deleteVector << element; numAppendedItems++; } } return numAppendedItems; } QColor KoChangeTracker::getInsertionBgColor() const { return d->insertionBgColor; } QColor KoChangeTracker::getDeletionBgColor() const { return d->deletionBgColor; } QColor KoChangeTracker::getFormatChangeBgColor() const { return d->formatChangeBgColor; } void KoChangeTracker::setInsertionBgColor(const QColor& bgColor) { d->insertionBgColor = bgColor; } void KoChangeTracker::setDeletionBgColor(const QColor& bgColor) { d->deletionBgColor = bgColor; } void KoChangeTracker::setFormatChangeBgColor(const QColor& bgColor) { d->formatChangeBgColor = bgColor; } ////A convenience function to get a ListIdType from a format //static KoListStyle::ListIdType ListId(const QTextListFormat &format) //{ // KoListStyle::ListIdType listId; // if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) { // listId = format.property(KoListStyle::ListId).toUInt(); // } // else { // listId = format.property(KoListStyle::ListId).toULongLong(); // } // return listId; //} QTextDocumentFragment KoChangeTracker::generateDeleteFragment(const QTextCursor &cursor) { QTextCursor editCursor(cursor); QTextDocument *document = cursor.document(); QTextDocument deletedDocument; QTextDocument deleteCursor(&deletedDocument); KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); if (textObjectManager) { for (int i = cursor.anchor();i <= cursor.position(); i++) { if (document->characterAt(i) == QChar::ObjectReplacementCharacter) { editCursor.setPosition(i+1); - } + } } } QTextBlock currentBlock = document->findBlock(cursor.anchor()); QTextBlock startBlock = currentBlock; QTextBlock endBlock = document->findBlock(cursor.position()).next(); currentBlock = document->findBlock(cursor.anchor()); startBlock = currentBlock; endBlock = document->findBlock(cursor.position()).next(); for (;currentBlock != endBlock; currentBlock = currentBlock.next()) { editCursor.setPosition(currentBlock.position()); if (editCursor.currentTable()) { QTextTableFormat tableFormat = editCursor.currentTable()->format(); editCursor.currentTable()->setFormat(tableFormat); } if (currentBlock != startBlock) { QTextBlockFormat blockFormat; editCursor.mergeBlockFormat(blockFormat); } } return cursor.selection(); } bool KoChangeTracker::checkListDeletion(const QTextList &list, const QTextCursor &cursor) { int startOfList = (list.item(0).position() - 1); int endOfList = list.item(list.count() -1).position() + list.item(list.count() -1).length() - 1; if ((cursor.anchor() <= startOfList) && (cursor.position() >= endOfList)) return true; else { /***************************************************************************************************/ /* Qt Quirk Work-Around */ /***************************************************************************************************/ if ((cursor.anchor() == (startOfList + 1)) && (cursor.position() > endOfList)) { return true; /***************************************************************************************************/ } else if((cursor.anchor() <= startOfList) && (list.count() == 1)) { return true; } else { return false; } } } void KoChangeTracker::insertDeleteFragment(QTextCursor &cursor) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { //This condition is for the work-around for a Qt behaviour //Even if a delete ends at the end of a table, the fragment will have an empty block after the table //If such a block is detected then, just ignore it if ((currentBlock.next() == tempDoc.end()) && (currentBlock.text().length() == 0) && (QTextCursor(currentBlock.previous()).currentTable())) { continue; } tempCursor.setPosition(currentBlock.position()); QTextList *textList = tempCursor.currentList(); int outlineLevel = currentBlock.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); KoList *currentList = KoTextDocument(cursor.document()).list(cursor.block()); int docOutlineLevel = cursor.block().blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); if (docOutlineLevel) { //Even though we got a list, it is actually a list for storing headings. So don't consider it currentList = 0; } QTextList *previousTextList = currentBlock.previous().isValid() ? QTextCursor(currentBlock.previous()).currentList():0; if (textList && previousTextList && (textList != previousTextList) && (KoList::level(currentBlock) == KoList::level(currentBlock.previous()))) { //Even though we are already in a list, the QTextList* of the current block is differnt from that of the previous block //Also the levels of the list-items ( previous and current ) are the same. //This can happen only when two lists are merged together without any intermediate content. //So we need to create a new list. currentList = 0; } if (textList) { if (deletedListItem && currentBlock != tempDoc.begin()) { // Found a deleted list item in the fragment. So insert a new list-item int deletedListItemLevel = KoList::level(currentBlock); if (!(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } else { cursor.mergeBlockFormat(currentBlock.blockFormat()); } if(!currentList) { if (!outlineLevel) { //This happens when a part of a paragraph and a succeeding list-item are deleted together //So go to the next block and insert it in the list there. QTextCursor tmp(cursor); tmp.setPosition(tmp.block().next().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } else { // This is a heading. So find the KoList for heading and add the block there KoList *headingList = KoTextDocument(cursor.document()).headingList(); currentList = headingList; } } currentList->add(cursor.block(), deletedListItemLevel); } } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); QTextTable *insertedTable = cursor.insertTable(numRows, numColumns, deletedTable->format()); for (int i=0; icellAt(i,j).firstCursorPosition().position()); tempCursor.setPosition(deletedTable->cellAt(i,j).lastCursorPosition().position(), QTextCursor::KeepAnchor); insertedTable->cellAt(i,j).setFormat(deletedTable->cellAt(i,j).format().toTableCellFormat()); cursor.setPosition(insertedTable->cellAt(i,j).firstCursorPosition().position()); cursor.insertFragment(tempCursor.selection()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); //Move the cursor outside of table cursor.setPosition(cursor.position() + 1); continue; } else { // This block does not contain a list. So no special work here. if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } if (QTextCursor(currentBlock.previous()).currentTable()) { cursor.mergeBlockFormat(currentBlock.blockFormat()); } } /********************************************************************************************************************/ /*This section of code is a work-around for a bug in the Qt. This work-around is safe. If and when the bug is fixed */ /*the if condition would never be true and the code would never get executed */ /********************************************************************************************************************/ if ((KoList::level(cursor.block()) != KoList::level(currentBlock)) && currentBlock.text().length()) { if (!currentList) { QTextCursor tmp(cursor); tmp.setPosition(tmp.block().previous().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } currentList->add(cursor.block(), KoList::level(currentBlock)); } /********************************************************************************************************************/ // Finally insert all the contents of the block into the main document. QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { cursor.insertText(currentFragment.text(), currentFragment.charFormat()); } } } } int KoChangeTracker::fragmentLength(const QTextDocumentFragment &fragment) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); tempCursor.insertFragment(fragment); int length = 0; bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); if (tempCursor.currentList()) { if (currentBlock != tempDoc.begin() && deletedListItem) length += 1; //For the Block separator } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); for (int i=0; icellAt(i,j).lastCursorPosition().position() - deletedTable->cellAt(i,j).firstCursorPosition().position()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); length += 1; continue; } else { if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) length += 1; //For the Block Separator } QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) length += currentFragment.text().length(); } } return length; } diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp index 0f4e92a38a..8412988323 100644 --- a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp +++ b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp @@ -1,1143 +1,1116 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2014 Denis Kuplyakov * * 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 "KoTextWriter_p.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 "TextDebug.h" #include #include // A convenience function to get a listId from a list-format static KoListStyle::ListIdType ListId(const QTextListFormat &format) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = format.property(KoListStyle::ListId).toUInt(); else listId = format.property(KoListStyle::ListId).toULongLong(); return listId; } typedef QPair Attribute; KoTextWriter::Private::Private(KoShapeSavingContext &context) : rdfData(0) , sharedData(0) , styleManager(0) , document(0) , writer(0) , context(context) { currentPairedInlineObjectsStack = new QStack(); writer = &context.xmlWriter(); } void KoTextWriter::Private::writeBlocks(QTextDocument *document, int from, int to, QHash &listStyles, QTextTable *currentTable, QTextList *currentList) { pairedInlineObjectsStackStack.push(currentPairedInlineObjectsStack); currentPairedInlineObjectsStack = new QStack(); QTextBlock block = document->findBlock(from); // Here we are going to detect all sections that // are positioned entirely inside selection. // They will stay untouched, and others will be omitted. // So we are using stack to detect them, by going through // the selection and finding open/close pairs. QSet entireWithinSectionNames; QStack sectionNamesStack; QTextCursor cur(document); cur.setPosition(from); while (to == -1 || cur.position() <= to) { if (cur.block().position() >= from) { // Begin of the block is inside selection. foreach (const KoSection *sec, KoSectionUtils::sectionStartings(cur.blockFormat())) { sectionNamesStack.push_back(sec->name()); } } if (to == -1 || cur.block().position() + cur.block().length() - 1 <= to) { // End of the block is inside selection. foreach (const KoSectionEnd *sec, KoSectionUtils::sectionEndings(cur.blockFormat())) { if (!sectionNamesStack.empty() && sectionNamesStack.top() == sec->name()) { sectionNamesStack.pop(); entireWithinSectionNames.insert(sec->name()); } } } if (!KoSectionUtils::getNextBlock(cur)) { break; } } while (block.isValid() && ((to == -1) || (block.position() <= to))) { QTextCursor cursor(block); int frameType = cursor.currentFrame()->format().intProperty(KoText::SubFrameType); if (frameType == KoText::AuxillaryFrameType) { break; // we've reached the "end" (end/footnotes saved by themselves) // note how NoteFrameType passes through here so the notes can // call writeBlocks to save their contents. } QTextBlockFormat format = block.blockFormat(); foreach (const KoSection *section, KoSectionUtils::sectionStartings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(section->name())) { section->saveOdf(context); } } if (format.hasProperty(KoParagraphStyle::HiddenByTable)) { block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::TableOfContentsData)) { saveTableOfContents(document, listStyles, block); block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::BibliographyData)) { saveBibliography(document, listStyles, block); block = block.next(); continue; } if (cursor.currentTable() && cursor.currentTable() != currentTable) { // Call the code to save the table.... saveTable(cursor.currentTable(), listStyles, from, to); // We skip to the end of the table. block = cursor.currentTable()->lastCursorPosition().block(); block = block.next(); continue; } if (cursor.currentList() && cursor.currentList() != currentList) { int previousBlockNumber = block.blockNumber(); block = saveList(block, listStyles, 1, currentTable); int blockNumberToProcess = block.blockNumber(); if (blockNumberToProcess != previousBlockNumber) continue; } saveParagraph(block, from, to); foreach (const KoSectionEnd *sectionEnd, KoSectionUtils::sectionEndings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(sectionEnd->name())) { sectionEnd->saveOdf(context); } } block = block.next(); } // while Q_ASSERT(!pairedInlineObjectsStackStack.isEmpty()); delete currentPairedInlineObjectsStack; currentPairedInlineObjectsStack = pairedInlineObjectsStackStack.pop(); } QHash KoTextWriter::Private::saveListStyles(QTextBlock block, int to) { QHash generatedLists; QHash listStyles; for (;block.isValid() && ((to == -1) || (block.position() < to)); block = block.next()) { QTextList *textList = block.textList(); if (!textList) continue; KoListStyle::ListIdType listId = ListId(textList->format()); if (KoList *list = KoTextDocument(document).list(listId)) { if (generatedLists.contains(list)) { if (!listStyles.contains(textList)) listStyles.insert(textList, generatedLists.value(list)); continue; } KoListStyle *listStyle = list->style(); if (!listStyle || listStyle->isOulineStyle()) continue; bool automatic = listStyle->styleId() == 0; KoGenStyle style(automatic ? KoGenStyle::ListAutoStyle : KoGenStyle::ListStyle); if (automatic && context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); listStyle->saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle->name(), listStyle->isNumberingStyle() ? KoGenStyles::AllowDuplicates : KoGenStyles::DontAddNumberToName); listStyles[textList] = generatedName; generatedLists.insert(list, generatedName); } else { if (listStyles.contains(textList)) continue; KoListLevelProperties llp = KoListLevelProperties::fromTextList(textList); KoGenStyle style(KoGenStyle::ListAutoStyle); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoListStyle listStyle; listStyle.setLevelProperties(llp); if (listStyle.isOulineStyle()) { continue; } listStyle.saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle.name()); listStyles[textList] = generatedName; } } return listStyles; } //---------------------------- PRIVATE ----------------------------------------------------------- void KoTextWriter::Private::openTagRegion(ElementType elementType, TagInformation& tagInformation) { //debugText << "tag:" << tagInformation.name() << openedTagStack.size(); if (tagInformation.name()) { writer->startElement(tagInformation.name(), elementType != ParagraphOrHeader); foreach (const Attribute &attribute, tagInformation.attributes()) { writer->addAttribute(attribute.first.toLocal8Bit(), attribute.second); } } openedTagStack.push(tagInformation.name()); //debugText << "stack" << openedTagStack.size(); } void KoTextWriter::Private::closeTagRegion() { // the tag needs to be closed even if there is no change tracking //debugText << "stack" << openedTagStack.size(); const char *tagName = openedTagStack.pop(); //debugText << "tag:" << tagName << openedTagStack.size(); if (tagName) { writer->endElement(); // close the tag } } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlock &block) { return KoTextWriter::saveParagraphStyle(block, styleManager, context); } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &charFormat) { return KoTextWriter::saveParagraphStyle(blockFormat, charFormat, styleManager, context); } QString KoTextWriter::Private::saveCharacterStyle(const QTextCharFormat &charFormat, const QTextCharFormat &blockCharFormat) { KoCharacterStyle *defaultCharStyle = styleManager->defaultCharacterStyle(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(charFormat.intProperty(KoCharacterStyle::StyleId)); if (!originalCharStyle) originalCharStyle = defaultCharStyle; QString generatedName; QString displayName = originalCharStyle->name(); QString internalName = QString(QUrl::toPercentEncoding(displayName, "", " ")).replace('%', '_'); KoCharacterStyle *autoStyle = originalCharStyle->autoStyle(charFormat, blockCharFormat); if (autoStyle->isEmpty()) { // This is the real, unmodified character style. if (originalCharStyle != defaultCharStyle) { KoGenStyle style(KoGenStyle::TextStyle, "text"); originalCharStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TextAutoStyle, "text", originalCharStyle != defaultCharStyle ? internalName : "" /*parent*/); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); autoStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, "T"); } delete autoStyle; return generatedName; } QString KoTextWriter::Private::saveTableStyle(const QTextTable& table) { KoTableStyle *originalTableStyle = styleManager->tableStyle(table.format().intProperty(KoTableStyle::StyleId)); QString generatedName; QString internalName; if (originalTableStyle) { internalName = QString(QUrl::toPercentEncoding(originalTableStyle->name(), "", " ")).replace('%', '_'); } KoTableStyle tableStyle(table.format()); if ((originalTableStyle) && (*originalTableStyle == tableStyle)) { // This is the real unmodified table style KoGenStyle style(KoGenStyle::TableStyle, "table"); originalTableStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TableAutoStyle, "table", internalName); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); if (originalTableStyle) tableStyle.removeDuplicates(*originalTableStyle); if (!tableStyle.isEmpty()) { tableStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, "Table"); } } return generatedName; } QString KoTextWriter::Private::saveTableColumnStyle(const KoTableColumnStyle& tableColumnStyle, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableColumnStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableRowStyle(const KoTableRowStyle& tableRowStyle, int rowNumber, const QString& tableStyleName) { QString generatedName = tableStyleName + '.' + QString::number(rowNumber + 1); KoGenStyle style(KoGenStyle::TableRowAutoStyle, "table-row"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableRowStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableCellStyle(const QTextTableCellFormat& cellFormat, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableCellAutoStyle, "table-cell"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoTableCellStyle cellStyle(cellFormat); cellStyle.saveOdf(style, context); generatedName = context.mainStyles().insert(style, generatedName); return generatedName; } -void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* rdf, TagInformation* tagInfos) +void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* , TagInformation* ) { - QBuffer rdfXmlData; - KoXmlWriter rdfXmlWriter(&rdfXmlData); - rdfXmlWriter.startDocument("rdf"); - rdfXmlWriter.startElement("rdf"); - rdf->saveOdf(context, &rdfXmlWriter); - rdfXmlWriter.endElement(); - rdfXmlWriter.endDocument(); - - KoXmlDocument xmlReader; - xmlReader.setContent(rdfXmlData.data(), true); - KoXmlElement mainElement = xmlReader.documentElement(); - foreach (const Attribute &attributeNameNS, mainElement.attributeFullNames()) { - QString attributeName = QString("%1:%2").arg(KoXmlNS::nsURI2NS(attributeNameNS.first)) - .arg(attributeNameNS.second); - if (attributeName.startsWith(':')) - attributeName.prepend("xml"); - tagInfos->addAttribute(attributeName, mainElement.attribute(attributeNameNS.second)); - } } /* Note on saving textranges: Start and end tags of textranges can appear on cursor positions in a text block. in front of the first text element, between the elements, or behind the last. A textblock is composed of no, one or many text fragments. If there is no fragment at all, the only possible cursor position is 0 (relative to the begin of the block). Example: ([] marks a block, {} a fragment) Three blocks, first with text fragments {AB} {C}, second empty, last with {DEF}. Possible positions are: [|{A|B}|{C}|] [|] [|{D|E|F}|] Start tags are ideally written in front of the content they are tagging, not behind the previous content. That way tags which are at the very begin of the complete document do not need special handling. End tags are ideally written directly behind the content, and not in front of the next content. That way end tags which are at the end of the complete document do not need special handling. Next there is the case of start tags which are at the final position of a text block: the content they belong to includes the block end/border, so they need to be written at the place of the last position. Then there is the case of end tags at the first position of a text block: the content they belong to includes the block start/border, so they need to be written at the place of the first position. Example: (< marks a start tag, > marks an end tag) [|>{
}|{}|<] [|><] [|>{||}|<] */ void KoTextWriter::Private::saveParagraph(const QTextBlock &block, int from, int to) { QTextCursor cursor(block); QTextBlockFormat blockFormat = block.blockFormat(); const int outlineLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); TagInformation blockTagInformation; if (outlineLevel > 0) { blockTagInformation.setTagName("text:h"); blockTagInformation.addAttribute("text:outline-level", outlineLevel); if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader) || blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)) { blockTagInformation.addAttribute("text:is-list-header", "true"); } } else { blockTagInformation.setTagName("text:p"); } openTagRegion(KoTextWriter::Private::ParagraphOrHeader, blockTagInformation); QString styleName = saveParagraphStyle(block); if (!styleName.isEmpty()) writer->addAttribute("text:style-name", styleName); KoElementReference xmlid; xmlid.invalidate(); QTextBlock currentBlock = block; KoTextBlockData blockData(currentBlock); if (blockData.saveXmlID()) { xmlid = context.xmlid(&blockData); xmlid.saveOdf(writer, KoElementReference::TextId); } // Write the fragments and their formats QTextCharFormat blockCharFormat = cursor.blockCharFormat(); QTextCharFormat previousCharFormat; QTextBlock::iterator it; if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(blockCharFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId() << "active xml id" << xmlid.toString(); inlineRdf->saveOdf(context, writer, xmlid); } const KoTextRangeManager *textRangeManager = KoTextDocument(block.document()).textRangeManager(); if (textRangeManager) { // write tags for ranges which end at the first position of the block const QHash endingTextRangesAtStart = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position(), globalFrom, globalTo); foreach (const KoTextRange *range, endingTextRangesAtStart) { range->saveOdf(context, block.position(), KoTextRange::EndTag); } } QString previousFragmentLink; // stores the end position of the last fragment, is position of the block without any fragment at all int lastEndPosition = block.position(); for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); const int fragmentStart = currentFragment.position(); const int fragmentEnd = fragmentStart + currentFragment.length(); if (to != -1 && fragmentStart >= to) break; if (currentFragment.isValid()) { QTextCharFormat charFormat = currentFragment.charFormat(); if ((!previousFragmentLink.isEmpty()) && (charFormat.anchorHref() != previousFragmentLink || !charFormat.isAnchor())) { // Close the current text:a closeTagRegion(); previousFragmentLink.clear(); } if (charFormat.isAnchor() && charFormat.anchorHref() != previousFragmentLink) { // Open a text:a previousFragmentLink = charFormat.anchorHref(); TagInformation linkTagInformation; if (charFormat.intProperty(KoCharacterStyle::AnchorType) == KoCharacterStyle::Bookmark) { linkTagInformation.setTagName("text:bookmark-ref"); QString href = previousFragmentLink.right(previousFragmentLink.size()-1); linkTagInformation.addAttribute("text:ref-name", href); //linkTagInformation.addAttribute("text:ref-format", add the style of the ref here); } else { linkTagInformation.setTagName("text:a"); linkTagInformation.addAttribute("xlink:type", "simple"); linkTagInformation.addAttribute("xlink:href", charFormat.anchorHref()); } if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(charFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId(); saveInlineRdf(inlineRdf, &linkTagInformation); } openTagRegion(KoTextWriter::Private::Span, linkTagInformation); } KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); KoInlineObject *inlineObject = textObjectManager ? textObjectManager->inlineTextObject(charFormat) : 0; // If we are in an inline object if (currentFragment.length() == 1 && inlineObject && currentFragment.text()[0].unicode() == QChar::ObjectReplacementCharacter) { bool saveInlineObject = true; if (KoTextMeta* z = dynamic_cast(inlineObject)) { if (z->position() < from) { // // This starts before the selection, default // to not saving it with special cases to allow saving // saveInlineObject = false; if (z->type() == KoTextMeta::StartBookmark) { if (z->endBookmark()->position() > from) { // // They have selected something starting after the // opening but before the // saveInlineObject = true; } } } } // get all text ranges which start before this inline object // or end directly after it (+1 to last position for that) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), currentFragment.position(), currentFragment.position()+1, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // get all text ranges which start before this const QList textRangesBefore = textRanges.values(currentFragment.position()); // write tags for ranges which start before this content or at positioned at it foreach (const KoTextRange *range, textRangesBefore) { range->saveOdf(context, currentFragment.position(), KoTextRange::StartTag); } bool saveSpan = dynamic_cast(inlineObject) != 0; if (saveSpan) { QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); } else { saveSpan = false; } } if (saveInlineObject) { inlineObject->saveOdf(context); } if (saveSpan) { writer->endElement(); } // write tags for ranges which end after this inline object const QList textRangesAfter = textRanges.values(currentFragment.position()+1); foreach (const KoTextRange *range, textRangesAfter) { range->saveOdf(context, currentFragment.position()+1, KoTextRange::EndTag); } // // Track the end marker for matched pairs so we produce valid // ODF // if (KoTextMeta* z = dynamic_cast(inlineObject)) { debugText << "found kometa, type:" << z->type(); if (z->type() == KoTextMeta::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoTextMeta::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }/* else if (KoBookmark* z = dynamic_cast(inlineObject)) { if (z->type() == KoBookmark::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoBookmark::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }*/ } else { // Normal block, easier to handle QString styleName = saveCharacterStyle(charFormat, blockCharFormat); TagInformation fragmentTagInformation; if (!styleName.isEmpty() /*&& !identical*/) { fragmentTagInformation.setTagName("text:span"); fragmentTagInformation.addAttribute("text:style-name", styleName); } openTagRegion(KoTextWriter::Private::Span, fragmentTagInformation); QString text = currentFragment.text(); int spanFrom = fragmentStart >= from ? fragmentStart : from; int spanTo = to == -1 ? fragmentEnd : (fragmentEnd > to ? to : fragmentEnd); // get all text ranges which change within this span // or end directly after it (+1 to last position to include those) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), spanFrom, spanTo, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // avoid mid, if possible if (spanFrom != fragmentStart || spanTo != fragmentEnd || !textRanges.isEmpty()) { if (textRanges.isEmpty()) { writer->addTextSpan(text.mid(spanFrom - fragmentStart, spanTo - spanFrom)); } else { // split the fragment into subspans at the points of range starts/ends QList subSpanTos = textRanges.uniqueKeys(); qSort(subSpanTos); // ensure last subSpanTo to be at the end if (subSpanTos.last() != spanTo) { subSpanTos.append(spanTo); } // spanFrom should not need to be included if (subSpanTos.first() == spanFrom) { subSpanTos.removeOne(spanFrom); } int subSpanFrom = spanFrom; // for all subspans foreach (int subSpanTo, subSpanTos) { // write tags for text ranges which start before this subspan or are positioned at it const QList textRangesStartingBefore = textRanges.values(subSpanFrom); foreach (const KoTextRange *range, textRangesStartingBefore) { range->saveOdf(context, subSpanFrom, KoTextRange::StartTag); } // write subspan content writer->addTextSpan(text.mid(subSpanFrom - fragmentStart, subSpanTo - subSpanFrom)); // write tags for text ranges which end behind this subspan const QList textRangesEndingBehind = textRanges.values(subSpanTo); foreach (const KoTextRange *range, textRangesEndingBehind) { range->saveOdf(context, subSpanTo, KoTextRange::EndTag); } subSpanFrom = subSpanTo; } } } else { writer->addTextSpan(text); } closeTagRegion(); } // if (inlineObject) previousCharFormat = charFormat; lastEndPosition = fragmentEnd; } } if (!previousFragmentLink.isEmpty()) { writer->endElement(); } if (it.atEnd() && textRangeManager && ((to == -1) || (lastEndPosition <= to))) { // write tags for ranges which start at the last position of the block, // i.e. at the position after the last (text) fragment const QHash startingTextRangesAtEnd = textRangeManager->textRangesChangingWithin(block.document(), lastEndPosition, lastEndPosition, globalFrom, globalTo); foreach (const KoTextRange *range, startingTextRangesAtEnd) { range->saveOdf(context, lastEndPosition, KoTextRange::StartTag); } } QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { QTextCharFormat charFormat; endCharStyle->applyStyle(charFormat); QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); writer->endElement(); } } } } if (to !=-1 && to < block.position() + block.length()) { foreach (KoInlineObject* inlineObject, *currentPairedInlineObjectsStack) { inlineObject->saveOdf(context); } } closeTagRegion(); } void KoTextWriter::Private::saveTable(QTextTable *table, QHash &listStyles, int from, int to) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(table); int numberHeadingRows = table->format().property(KoTableStyle::NumberHeadingRows).toInt(); TagInformation tableTagInformation; QString tableStyleName = saveTableStyle(*table); tableTagInformation.setTagName("table:table"); tableTagInformation.addAttribute("table:style-name", tableStyleName); if (table->format().boolProperty(KoTableStyle::TableIsProtected)) { tableTagInformation.addAttribute("table:protected", "true"); } if (table->format().hasProperty(KoTableStyle::TableTemplate)) { tableTagInformation.addAttribute("table:template-name", sharedData->styleName(table->format().intProperty(KoTableStyle::TableTemplate))); } if (table->format().boolProperty(KoTableStyle::UseBandingColumnStyles)) { tableTagInformation.addAttribute("table:use-banding-columns-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseBandingRowStyles)) { tableTagInformation.addAttribute("table:use-banding-rows-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstColumnStyles)) { tableTagInformation.addAttribute("table:use-first-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstRowStyles)) { tableTagInformation.addAttribute("table:use-first-row-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastColumnStyles)) { tableTagInformation.addAttribute("table:use-last-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastRowStyles)) { tableTagInformation.addAttribute("table:use-last-row-styles", "true"); } int firstColumn = 0; int lastColumn = table->columns() -1; int firstRow = 0; int lastRow = table->rows() -1; if (to != -1 && from >= table->firstPosition() && to <= table->lastPosition()) { firstColumn = table->cellAt(from).column(); firstRow = table->cellAt(from).row(); lastColumn = table->cellAt(to).column(); lastRow = table->cellAt(to).row(); if (firstColumn == lastColumn && firstRow == lastRow && from >= table->firstPosition()) { // we only selected something inside a single cell so don't save a table writeBlocks(table->document(), from, to, listStyles, table); return; } } openTagRegion(KoTextWriter::Private::Table, tableTagInformation); for (int c = firstColumn ; c <= lastColumn; c++) { KoTableColumnStyle columnStyle = tcarManager.columnStyle(c); int repetition = 0; for (; repetition <= (lastColumn - c) ; repetition++) { if (columnStyle != tcarManager.columnStyle(c + repetition + 1)) break; } TagInformation tableColumnInformation; tableColumnInformation.setTagName("table:table-column"); QString columnStyleName = saveTableColumnStyle(columnStyle, c, tableStyleName); tableColumnInformation.addAttribute("table:style-name", columnStyleName); if (repetition > 0) tableColumnInformation.addAttribute("table:number-columns-repeated", repetition + 1); openTagRegion(KoTextWriter::Private::TableColumn, tableColumnInformation); closeTagRegion(); c += repetition; } if (numberHeadingRows) writer->startElement("table:table-header-rows"); // TODO make work for copying part of table that has header rows - copy header rows additionally or not ? for (int r = firstRow; r <= lastRow; r++) { TagInformation tableRowInformation; tableRowInformation.setTagName("table:table-row"); KoTableRowStyle rowStyle = tcarManager.rowStyle(r); if (!rowStyle.isEmpty()) { QString rowStyleName = saveTableRowStyle(rowStyle, r, tableStyleName); tableRowInformation.addAttribute("table:style-name", rowStyleName); } openTagRegion(KoTextWriter::Private::TableRow, tableRowInformation); for (int c = firstColumn; c <= lastColumn; c++) { QTextTableCell cell = table->cellAt(r, c); TagInformation tableCellInformation; if ((cell.row() == r) && (cell.column() == c)) { tableCellInformation.setTagName("table:table-cell"); if (cell.rowSpan() > 1) tableCellInformation.addAttribute("table:number-rows-spanned", cell.rowSpan()); if (cell.columnSpan() > 1) tableCellInformation.addAttribute("table:number-columns-spanned", cell.columnSpan()); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } // Save the Rdf for the table cell QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); QVariant v = cellFormat.property(KoTableCellStyle::InlineRdf); if (KoTextInlineRdf* inlineRdf = v.value()) { inlineRdf->saveOdf(context, writer); } QString cellStyleName = saveTableCellStyle(cellFormat, c, tableStyleName); tableCellInformation.addAttribute("table:style-name", cellStyleName); openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); writeBlocks(table->document(), cell.firstPosition(), cell.lastPosition(), listStyles, table); } else { tableCellInformation.setTagName("table:covered-table-cell"); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); } closeTagRegion(); } closeTagRegion(); if (r + 1 == numberHeadingRows) { writer->endElement(); // table:table-header-rows writer->startElement("table:table-rows"); } } if (numberHeadingRows) writer->endElement(); // table:table-rows closeTagRegion(); } void KoTextWriter::Private::saveTableOfContents(QTextDocument *document, QHash &listStyles, QTextBlock toc) { Q_UNUSED(document); writer->startElement("text:table-of-content"); KoTableOfContentsGeneratorInfo *info = toc.blockFormat().property(KoParagraphStyle::TableOfContentsData).value(); QTextDocument *tocDocument = toc.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = tocDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writer->addAttribute("text:name", QString("%1_Head").arg(info->m_name)); writeBlocks(tocDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(tocDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:table-of-content } void KoTextWriter::Private::saveBibliography(QTextDocument *document, QHash &listStyles, QTextBlock bib) { Q_UNUSED(document); writer->startElement("text:bibliography"); KoBibliographyInfo *info = bib.blockFormat().property(KoParagraphStyle::BibliographyData).value(); QTextDocument *bibDocument = bib.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = bibDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writeBlocks(bibDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(bibDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:bibliography } QTextBlock& KoTextWriter::Private::saveList(QTextBlock &block, QHash &listStyles, int level, QTextTable *currentTable) { QTextList *textList, *topLevelTextList; topLevelTextList = textList = block.textList(); int headingLevel = 0, numberedParagraphLevel = 0; QTextBlockFormat blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); KoTextDocument textDocument(block.document()); KoList *list = textDocument.list(block); int topListLevel = KoList::level(block); bool listStarted = false; if (!headingLevel && !numberedParagraphLevel) { listStarted = true; TagInformation listTagInformation; listTagInformation.setTagName("text:list"); listTagInformation.addAttribute("text:style-name", listStyles[textList]); if (list && listXmlIds.contains(list->listContinuedFrom())) { listTagInformation.addAttribute("text:continue-list", listXmlIds.value(list->listContinuedFrom())); } QString listXmlId = QString("list-%1").arg(createXmlId()); listTagInformation.addAttribute("xml:id", listXmlId); if (! listXmlIds.contains(list)) { listXmlIds.insert(list, listXmlId); } openTagRegion(KoTextWriter::Private::List, listTagInformation); } if (!headingLevel) { do { if (numberedParagraphLevel) { TagInformation paraTagInformation; paraTagInformation.setTagName("text:numbered-paragraph"); paraTagInformation.addAttribute("text:level", numberedParagraphLevel); paraTagInformation.addAttribute("text:style-name", listStyles.value(textList)); QString listId = numberedParagraphListIds.value(list, QString("list-%1").arg(createXmlId())); numberedParagraphListIds.insert(list, listId); paraTagInformation.addAttribute("text:list-id", listId); openTagRegion(KoTextWriter::Private::NumberedParagraph, paraTagInformation); writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); closeTagRegion(); } else { const bool listHeader = blockFormat.boolProperty(KoParagraphStyle::IsListHeader)|| blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem); TagInformation listItemTagInformation; listItemTagInformation.setTagName(listHeader ? "text:list-header" : "text:list-item"); if (block.blockFormat().hasProperty(KoParagraphStyle::ListStartValue)) { int startValue = block.blockFormat().intProperty(KoParagraphStyle::ListStartValue); listItemTagInformation.addAttribute("text:start-value", startValue); } if (textList == topLevelTextList) { openTagRegion(KoTextWriter::Private::ListItem, listItemTagInformation); } else { // This is a sub-list. So check for a list-change openTagRegion(KoTextWriter::Private::List, listItemTagInformation); } if (KoListStyle::isNumberingStyle(textList->format().style())) { KoTextBlockData blockData(block); writer->startElement("text:number", false); writer->addTextSpan(blockData.counterText()); writer->endElement(); } if (topListLevel == level && textList == topLevelTextList) { writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); // we are generating a text:list-item. Look forward and generate unnumbered list items. while (true) { QTextBlock nextBlock = block.next(); if (!nextBlock.textList() || !nextBlock.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) break; block = nextBlock; saveParagraph(block, block.position(), block.position() + block.length() - 1); } } else { //This is a sub-list while (KoList::level(block) >= (level + 1) && !(headingLevel || numberedParagraphLevel)) { block = saveList(block, listStyles, level + 1, currentTable); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); } //saveList will return a block one-past the last block of the list. //Since we are doing a block.next() below, we need to go one back. block = block.previous(); } closeTagRegion(); } block = block.next(); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); textList = block.textList(); } while ((textDocument.list(block) == list) && (KoList::level(block) >= topListLevel)); } if (listStarted) { closeTagRegion(); } return block; } void KoTextWriter::Private::addNameSpaceDefinitions(QString &generatedXmlString) { //Generate the name-space definitions so that it can be parsed. Like what is office:text, office:delta etc QString nameSpaceDefinitions; QTextStream nameSpacesStream(&nameSpaceDefinitions); nameSpacesStream.setCodec("UTF-8"); nameSpacesStream << ""; generatedXmlString.prepend(nameSpaceDefinitions); generatedXmlString.append(""); } -void KoTextWriter::Private::writeAttributes(QTextStream &outputXmlStream, KoXmlElement &element) +void KoTextWriter::Private::writeAttributes(QTextStream &, KoXmlElement &) { - QList > attributes = element.attributeFullNames(); - foreach (const Attribute &attributeNamePair, attributes) { - if (attributeNamePair.first == KoXmlNS::text) { - outputXmlStream << " text:" << attributeNamePair.second << "="; - outputXmlStream << "\"" << element.attributeNS(KoXmlNS::text, attributeNamePair.second) << "\""; - } else { - //To Be Added when needed - } - } } void KoTextWriter::Private::writeNode(QTextStream &outputXmlStream, KoXmlNode &node, bool writeOnlyChildren) { if (node.isText()) { outputXmlStream << node.toText().data(); } else if (node.isElement()) { KoXmlElement element = node.toElement(); - if ((element.localName() == "removed-content") && !element.childNodesCount()) { + if ((element.localName() == "removed-content") && !KoXml::childNodesCount(element)) { return; } if (!writeOnlyChildren) { outputXmlStream << "<" << element.prefix() << ":" << element.localName(); writeAttributes(outputXmlStream,element); outputXmlStream << ">"; } for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { writeNode(outputXmlStream, node); } if (!writeOnlyChildren) { outputXmlStream << ""; } } } QString KoTextWriter::Private::createXmlId() { QString uuid = QUuid::createUuid().toString(); uuid.remove('{'); uuid.remove('}'); return uuid; } diff --git a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp index 0005a3f5e9..b9e3ba7d02 100644 --- a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp @@ -1,1057 +1,1051 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 KO GmbH * 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 "KoTableCellStyle.h" #include "KoTableCellStyle_p.h" #include #include #include #include #include "KoParagraphStyle.h" #include #include #include #include #include #include #include #include #include "TextDebug.h" #include KoTableCellStyle::RotationAlignment rotationAlignmentFromString(const QString& align) { if (align == "bottom") return KoTableCellStyle::RAlignBottom; if (align == "center") return KoTableCellStyle::RAlignCenter; if (align == "top") return KoTableCellStyle::RAlignTop; - + return KoTableCellStyle::RAlignNone; } QString rotationAlignmentToString(KoTableCellStyle::RotationAlignment align) { if (align == KoTableCellStyle::RAlignBottom) return "bottom"; if (align == KoTableCellStyle::RAlignTop) return "top"; if (align == KoTableCellStyle::RAlignCenter) return "center"; return "none"; } KoTableCellStylePrivate::KoTableCellStylePrivate() : paragraphStyle(0) , parentStyle(0) , next(0) { } KoTableCellStylePrivate::~KoTableCellStylePrivate() { } void KoTableCellStylePrivate::setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } KoTableCellStyle::KoTableCellStyle(QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const QTextTableCellFormat &format, QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->stylesPrivate = format.properties(); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const KoTableCellStyle &other) :QObject(other.parent()) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); } KoTableCellStyle& KoTableCellStyle::operator=(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); if (this == &other) { return *this; } copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); return *this; } KoTableCellStyle::~KoTableCellStyle() { delete d_ptr; } KoTableCellStyle *KoTableCellStyle::fromTableCell(const QTextTableCell &tableCell, QObject *parent) { QTextTableCellFormat tableCellFormat = tableCell.format().toTableCellFormat(); return new KoTableCellStyle(tableCellFormat, parent); } QTextCharFormat KoTableCellStyle::cleanCharFormat(const QTextCharFormat &charFormat) { if (charFormat.isTableCellFormat()) { QTextTableCellFormat format; const QMap props = charFormat.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { // lets save all Qt's table cell properties if (it.key()>=QTextFormat::TableCellRowSpan && it.key()=StyleId && it.key()parentStyle = parent; } void KoTableCellStyle::setLeftPadding(qreal padding) { setProperty(QTextFormat::TableCellLeftPadding, padding); } void KoTableCellStyle::setTopPadding(qreal padding) { setProperty(QTextFormat::TableCellTopPadding, padding); } void KoTableCellStyle::setRightPadding(qreal padding) { setProperty(QTextFormat::TableCellRightPadding, padding); } void KoTableCellStyle::setBottomPadding(qreal padding) { setProperty(QTextFormat::TableCellBottomPadding, padding); } qreal KoTableCellStyle::leftPadding() const { return propertyDouble(QTextFormat::TableCellLeftPadding); } qreal KoTableCellStyle::rightPadding() const { return propertyDouble(QTextFormat::TableCellRightPadding); } qreal KoTableCellStyle::topPadding() const { return propertyDouble(QTextFormat::TableCellTopPadding); } qreal KoTableCellStyle::bottomPadding() const { return propertyDouble(QTextFormat::TableCellBottomPadding); } void KoTableCellStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } KoParagraphStyle *KoTableCellStyle::paragraphStyle() const { Q_D(const KoTableCellStyle); return d->paragraphStyle; } bool KoTableCellStyle::shrinkToFit() const { return propertyBoolean(ShrinkToFit); } void KoTableCellStyle::setShrinkToFit(bool state) { setProperty(ShrinkToFit, state); } void KoTableCellStyle::setProperty(int key, const QVariant &value) { Q_D(KoTableCellStyle); 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 KoTableCellStyle::remove(int key) { Q_D(KoTableCellStyle); d->stylesPrivate.remove(key); } QVariant KoTableCellStyle::value(int key) const { Q_D(const KoTableCellStyle); QVariant var = d->stylesPrivate.value(key); if (var.isNull() && d->parentStyle) var = d->parentStyle->value(key); return var; } bool KoTableCellStyle::hasProperty(int key) const { Q_D(const KoTableCellStyle); return d->stylesPrivate.contains(key); } qreal KoTableCellStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QPen KoTableCellStyle::propertyPen(int key) const { const QVariant prop = value(key); if (prop.userType() != QVariant::Pen) return QPen(Qt::NoPen); return qvariant_cast(prop); } int KoTableCellStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoTableCellStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoTableCellStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoTableCellStyle::applyStyle(QTextTableCellFormat &format) const { Q_D(const KoTableCellStyle); 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); } // Hack : build KoBorder here if (d->parentStyle && d->parentStyle->hasProperty(Borders) && this->hasProperty(Borders)) { KoBorder parentBorder = d->parentStyle->borders(); KoBorder childBorder = this->borders(); if (childBorder.hasBorder(KoBorder::LeftBorder)) parentBorder.setBorderData(KoBorder::LeftBorder, childBorder.borderData(KoBorder::LeftBorder)); if (childBorder.hasBorder(KoBorder::RightBorder)) parentBorder.setBorderData(KoBorder::RightBorder, childBorder.borderData(KoBorder::RightBorder)); if (childBorder.hasBorder(KoBorder::TopBorder)) parentBorder.setBorderData(KoBorder::TopBorder, childBorder.borderData(KoBorder::TopBorder)); if (childBorder.hasBorder(KoBorder::BottomBorder)) parentBorder.setBorderData(KoBorder::BottomBorder, childBorder.borderData(KoBorder::BottomBorder)); if (childBorder.hasBorder(KoBorder::BltrBorder)) parentBorder.setBorderData(KoBorder::BltrBorder, childBorder.borderData(KoBorder::BltrBorder)); if (childBorder.hasBorder(KoBorder::TlbrBorder)) parentBorder.setBorderData(KoBorder::TlbrBorder, childBorder.borderData(KoBorder::TlbrBorder)); format.setProperty(Borders, QVariant::fromValue(parentBorder)); } } void KoTableCellStyle::applyStyle(QTextTableCell &cell) const { Q_D(const KoTableCellStyle); QTextTableCellFormat format = cell.format().toTableCellFormat(); applyStyle(format); if (d->paragraphStyle) { d->paragraphStyle->KoCharacterStyle::applyStyle(format); } cell.setFormat(format); } void KoTableCellStyle::setBackground(const QBrush &brush) { setProperty(CellBackgroundBrush, brush); } void KoTableCellStyle::clearBackground() { Q_D(KoTableCellStyle); d->stylesPrivate.remove(CellBackgroundBrush); } QBrush KoTableCellStyle::background() const { Q_D(const KoTableCellStyle); QVariant variant = d->stylesPrivate.value(CellBackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoTableCellStyle::setWrap(bool state) { setProperty(Wrap, state); } bool KoTableCellStyle::wrap() const { return propertyBoolean(Wrap); } void KoTableCellStyle::setAlignment(Qt::Alignment alignment) { setProperty(VerticalAlignment, (int) alignment); } Qt::Alignment KoTableCellStyle::alignment() const { if (propertyInt(VerticalAlignment) == 0) return Qt::AlignTop; return static_cast(propertyInt(VerticalAlignment)); } KoTableCellStyle *KoTableCellStyle::parentStyle() const { Q_D(const KoTableCellStyle); return d->parentStyle; } QString KoTableCellStyle::name() const { Q_D(const KoTableCellStyle); return d->name; } void KoTableCellStyle::setName(const QString &name) { Q_D(KoTableCellStyle); if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoTableCellStyle::styleId() const { return propertyInt(StyleId); } void KoTableCellStyle::setStyleId(int id) { Q_D(KoTableCellStyle); setProperty(StyleId, id); if (d->next == 0) d->next = id; } QString KoTableCellStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoTableCellStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoTableCellStyle::setCellProtection(KoTableCellStyle::CellProtectionFlag protection) { setProperty(CellProtection, protection); } KoTableCellStyle::CellProtectionFlag KoTableCellStyle::cellProtection() const { return (CellProtectionFlag) propertyInt(CellProtection); } void KoTableCellStyle::setTextDirection(KoText::Direction value) { setProperty(TextWritingMode, value); } KoText::Direction KoTableCellStyle::textDirection() const { return (KoText::Direction) propertyInt(TextWritingMode); } bool KoTableCellStyle::printContent() const { return (hasProperty(PrintContent) && propertyBoolean(PrintContent)); } void KoTableCellStyle::setPrintContent(bool state) { setProperty(PrintContent, state); } bool KoTableCellStyle::repeatContent() const { return (hasProperty(RepeatContent) && propertyBoolean(RepeatContent)); } void KoTableCellStyle::setRepeatContent(bool state) { setProperty(RepeatContent, state); } int KoTableCellStyle::decimalPlaces() const { return propertyInt(DecimalPlaces); } void KoTableCellStyle::setDecimalPlaces(int places) { setProperty(DecimalPlaces, places); } bool KoTableCellStyle::alignFromType() const { return (hasProperty(AlignFromType) && propertyBoolean(AlignFromType)); } void KoTableCellStyle::setAlignFromType(bool state) { setProperty(AlignFromType, state); } qreal KoTableCellStyle::rotationAngle() const { return propertyDouble(RotationAngle); } void KoTableCellStyle::setRotationAngle(qreal value) { if (value >= 0) setProperty(RotationAngle, value); } void KoTableCellStyle::setVerticalGlyphOrientation(bool state) { setProperty(VerticalGlyphOrientation, state); } bool KoTableCellStyle::verticalGlyphOrientation() const { if (hasProperty(VerticalGlyphOrientation)) return propertyBoolean(VerticalGlyphOrientation); return true; } void KoTableCellStyle::setDirection(KoTableCellStyle::CellTextDirection direction) { setProperty(Direction, direction); } KoBorder KoTableCellStyle::borders() const { if (hasProperty(Borders)) return value(Borders).value(); return KoBorder(); } void KoTableCellStyle::setBorders(const KoBorder& borders) { setProperty(Borders, QVariant::fromValue(borders)); } KoShadowStyle KoTableCellStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoTableCellStyle::setShadow(const KoShadowStyle& shadow) { setProperty(Shadow, QVariant::fromValue(shadow)); } KoTableCellStyle::RotationAlignment KoTableCellStyle::rotationAlignment() const { return static_cast(propertyInt(RotationAlign)); } void KoTableCellStyle::setRotationAlignment(KoTableCellStyle::RotationAlignment align) { setProperty(RotationAlign, align); } KoTableCellStyle::CellTextDirection KoTableCellStyle::direction() const { if (hasProperty(Direction)) return (KoTableCellStyle::CellTextDirection) propertyInt(Direction); return KoTableCellStyle::Default; } void KoTableCellStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); Q_D(KoTableCellStyle); 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()); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } paragraphStyle()->loadOdf(element, scontext, true); // load the par and char properties - - // Borders - we don't handle inheritance unfortunately - hope it's not a big problem - KoBorder borders = this->borders(); - borders.loadOdf(element->namedItemNS(KoXmlNS::style, "table-cell-properties").toElement()); - setBorders(borders); - context.styleStack().save(); QString family = element->attributeNS(KoXmlNS::style, "family", "table-cell"); context.addStyles(element, family.toLocal8Bit().constData()); // Load all parents - only because we don't support inheritance. context.styleStack().setTypeProperties("table-cell"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().setTypeProperties("graphic"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().restore(); } void KoTableCellStyle::loadOdfProperties(KoShapeLoadingContext &context, KoStyleStack &styleStack) { // Padding if (styleStack.hasProperty(KoXmlNS::fo, "padding-left")) setLeftPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-left"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-right")) setRightPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-right"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-top")) setTopPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-top"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-bottom")) setBottomPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-bottom"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding")) setPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding"))); if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) { setShadow(shadow); } } // The fo:background-color attribute specifies the background color of a cell. if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") setBackground(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format setBackground(brush); } } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "solid" || fillStyle == "hatch") { styleStack.save(); QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.odfLoadingContext().stylesReader()); setBackground(brush); styleStack.restore(); } if (styleStack.hasProperty(KoXmlNS::style, "shrink-to-fit")) { setShrinkToFit(styleStack.property(KoXmlNS::style, "shrink-to-fit") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "print-content")) { setPrintContent(styleStack.property(KoXmlNS::style, "print-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "decimal-places")) { bool ok; int value = styleStack.property(KoXmlNS::style, "decimal-places").toInt(&ok); if (ok) setDecimalPlaces(value); } - + if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) { setRotationAngle(KoUnit::parseAngle(styleStack.property(KoXmlNS::style, "rotation-angle"))); } - + if (styleStack.hasProperty(KoXmlNS::style, "glyph-orientation-vertical")) { setVerticalGlyphOrientation(styleStack.property(KoXmlNS::style, "glyph-orientation-vertical") == "auto"); } - + if (styleStack.hasProperty(KoXmlNS::style, "direction")) { if (styleStack.property(KoXmlNS::style, "direction") == "ltr") setDirection(KoTableCellStyle::LeftToRight); else setDirection(KoTableCellStyle::TopToBottom); } - + if (styleStack.hasProperty(KoXmlNS::style, "rotation-align")) { setRotationAlignment(rotationAlignmentFromString(styleStack.property(KoXmlNS::style, "rotation-align"))); } - + if (styleStack.hasProperty(KoXmlNS::style, "text-align-source")) { setAlignFromType(styleStack.property(KoXmlNS::style, "text-align-source") == "value-type"); } - + if (styleStack.hasProperty(KoXmlNS::fo, "wrap-option")) { setWrap(styleStack.property(KoXmlNS::fo, "wrap-option") == "wrap"); } - + if (styleStack.hasProperty(KoXmlNS::style, "cell-protect")) { QString protection = styleStack.property(KoXmlNS::style, "cell-protect"); if (protection == "none") setCellProtection(NoProtection); else if (protection == "hidden-and-protected") setCellProtection(HiddenAndProtected); else if (protection == "protected") setCellProtection(Protected); else if (protection == "formula-hidden") setCellProtection(FormulaHidden); else if ((protection == "protected formula-hidden") || (protection == "formula-hidden protected")) setCellProtection(ProtectedAndFormulaHidden); } // Alignment const QString verticalAlign(styleStack.property(KoXmlNS::style, "vertical-align")); if (!verticalAlign.isEmpty()) { if (verticalAlign == "automatic") setAlignment((Qt::AlignmentFlag) 0); else setAlignment(KoText::valignmentFromString(verticalAlign)); } - + if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) setTextDirection(KoText::directionFromString(styleStack.property(KoXmlNS::style, "writing-mode"))); } void KoTableCellStyle::copyProperties(const KoTableCellStyle *style) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *styleD = static_cast(style->d_func()); d->stylesPrivate = styleD->stylesPrivate; setName(style->name()); // make sure we emit property change d->next = styleD->next; d->parentStyle = styleD->parentStyle; } KoTableCellStyle *KoTableCellStyle::clone(QObject *parent) { KoTableCellStyle *newStyle = new KoTableCellStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoTableCellStyle::operator==(const KoTableCellStyle &other) const { Q_D(const KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); return otherD->stylesPrivate == d->stylesPrivate; } void KoTableCellStyle::removeDuplicates(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); d->stylesPrivate.removeDuplicates(otherD->stylesPrivate); } void KoTableCellStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoTableCellStyle); QList keys = d->stylesPrivate.keys(); bool donePadding = false; - if (hasProperty(QTextFormat::TableCellLeftPadding) && - hasProperty(QTextFormat::TableCellRightPadding) && - hasProperty(QTextFormat::TableCellTopPadding) && - hasProperty(QTextFormat::TableCellBottomPadding) && + if (hasProperty(QTextFormat::TableCellLeftPadding) && + hasProperty(QTextFormat::TableCellRightPadding) && + hasProperty(QTextFormat::TableCellTopPadding) && + hasProperty(QTextFormat::TableCellBottomPadding) && leftPadding() == rightPadding() && topPadding() == bottomPadding() && topPadding() == leftPadding()) { donePadding = true; style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::TableCellType); } Q_FOREACH (int key, keys) { if (key == CellBackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::TableCellType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::TableCellType); } else if (key == VerticalAlignment) { if (propertyInt(VerticalAlignment) == 0) style.addProperty("style:vertical-align", "automatic", KoGenStyle::TableCellType); else style.addProperty("style:vertical-align", KoText::valignmentToString(alignment()), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellLeftPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellRightPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellTopPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellBottomPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::TableCellType); } else if (key == ShrinkToFit) { style.addProperty("style:shrink-to-fit", shrinkToFit(), KoGenStyle::TableCellType); } else if (key == PrintContent) { style.addProperty("style:print-content", printContent(), KoGenStyle::TableCellType); } else if (key == RepeatContent) { style.addProperty("style:repeat-content", repeatContent(), KoGenStyle::TableCellType); } else if (key == DecimalPlaces) { style.addProperty("style:decimal-places", decimalPlaces(), KoGenStyle::TableCellType); } else if (key == RotationAngle) { style.addProperty("style:rotation-angle", QString::number(rotationAngle()), KoGenStyle::TableCellType); } else if (key == Wrap) { if (wrap()) style.addProperty("fo:wrap-option", "wrap", KoGenStyle::TableCellType); else style.addProperty("fo:wrap-option", "no-wrap", KoGenStyle::TableCellType); } else if (key == Direction) { if (direction() == LeftToRight) style.addProperty("style:direction", "ltr", KoGenStyle::TableCellType); else if (direction() == TopToBottom) style.addProperty("style:direction", "ttb", KoGenStyle::TableCellType); } else if (key == CellProtection) { if (cellProtection() == NoProtection) style.addProperty("style:cell-protect", "none", KoGenStyle::TableCellType); else if (cellProtection() == HiddenAndProtected) style.addProperty("style:cell-protect", "hidden-and-protected", KoGenStyle::TableCellType); else if (cellProtection() == Protected) style.addProperty("style:cell-protect", "protected", KoGenStyle::TableCellType); else if (cellProtection() == FormulaHidden) style.addProperty("style:cell-protect", "formula-hidden", KoGenStyle::TableCellType); else if (cellProtection() == ProtectedAndFormulaHidden) style.addProperty("style:cell-protect", "protected formula-hidden", KoGenStyle::TableCellType); } else if (key == AlignFromType) { if (alignFromType()) style.addProperty("style:text-align-source", "value-type", KoGenStyle::TableCellType); else style.addProperty("style:text-align-source", "fix", KoGenStyle::TableCellType); } else if (key == RotationAlign) { style.addProperty("style:rotation-align", rotationAlignmentToString(rotationAlignment()), KoGenStyle::TableCellType); } else if (key == TextWritingMode) { style.addProperty("style:writing-mode", KoText::directionToString(textDirection()), KoGenStyle::TableCellType); } else if (key == VerticalGlyphOrientation) { if (verticalGlyphOrientation()) style.addProperty("style:glyph-orientation-vertical", "auto", KoGenStyle::TableCellType); else style.addProperty("style:glyph-orientation-vertical", "0", KoGenStyle::TableCellType); } else if (key == Borders) { borders().saveOdf(style, KoGenStyle::TableCellType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (d->paragraphStyle) { d->paragraphStyle->saveOdf(style, context); } } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, KoBorder::BorderStyle style, qreal width, const QColor &color) { KoBorder::BorderData edge; qreal innerWidth = 0; qreal middleWidth = 0; qreal space = 0; QVector dashes; switch (style) { case KoBorder::BorderNone: width = 0.0; break; case KoBorder::BorderDouble: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDotted: dashes << 1 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashed: dashes << 4 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashedLong: { dashes << 4 << 4; edge.outerPen.setDashPattern(dashes); break; } case KoBorder::BorderTriple: innerWidth = middleWidth = space = width/6; width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDashDot: dashes << 3 << 3<< 7 << 3; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashDotDot: dashes << 2 << 2<< 6 << 2 << 2 << 2; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderWave: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderSlash: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDoubleWave: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; default: edge.outerPen.setStyle(Qt::SolidLine); break; } edge.outerPen.setColor(color); edge.outerPen.setJoinStyle(Qt::MiterJoin); edge.outerPen.setCapStyle(Qt::FlatCap); edge.outerPen.setWidthF(width); edge.spacing = space; edge.innerPen = edge.outerPen; edge.innerPen.setWidthF(innerWidth); QPen middlePen; middlePen = edge.outerPen; middlePen.setWidthF(middleWidth); setEdge(side, edge, style); } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, const KoBorder::BorderData &edge, KoBorder::BorderStyle style) { KoBorder borders = this->borders(); KoBorder::BorderData edgeCopy(edge); edgeCopy.style = style; // Just for safety. borders.setBorderData(side, edgeCopy); setBorders(borders); } void KoTableCellStyle::setEdgeDoubleBorderValues(KoBorder::BorderSide side, qreal innerWidth, qreal space) { KoBorder::BorderData edge = getEdge(side); qreal totalWidth = edge.outerPen.widthF() + edge.spacing + edge.innerPen.widthF(); if (edge.innerPen.widthF() > 0.0) { edge.outerPen.setWidthF(totalWidth - innerWidth - space); edge.spacing = space; edge.innerPen.setWidthF(innerWidth); setEdge(side, edge, getBorderStyle(side)); } } bool KoTableCellStyle::hasBorders() const { return borders().hasBorder(); } qreal KoTableCellStyle::leftBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::rightBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::topBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::leftInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::rightInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::topInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::bottomInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::leftOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::rightOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::topOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.outerPen.widthF(); } KoBorder::BorderData KoTableCellStyle::getEdge(KoBorder::BorderSide side) const { KoBorder border = this->borders(); return border.borderData(side); } KoBorder::BorderStyle KoTableCellStyle::getBorderStyle(KoBorder::BorderSide side) const { KoBorder::BorderData edge = getEdge(side); return edge.style; } diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 20dd1a6b9f..79b5976bfa 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,353 +1,353 @@ /* * Copyright (C) 2016 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 "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return KisImageBuilder_RESULT_FAILURE; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc; bool ok = oldLoadAndParse(m_store, "root", doc); if (ok) ok = loadXML(doc, m_store); if (!ok) { return KisImageBuilder_RESULT_FAILURE; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return KisImageBuilder_RESULT_FAILURE; } if (m_store->hasFile("documentinfo.xml")) { - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc; if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImageBuilder_Result KraConverter::buildFile(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return KisImageBuilder_RESULT_FAILURE; } bool result = false; m_kraSaver = new KisKraSaver(m_doc); result = saveRootDocuments(m_store); if (!result) { return KisImageBuilder_RESULT_FAILURE; } result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } if (!m_store->finalize()) { return KisImageBuilder_RESULT_FAILURE; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return KisImageBuilder_RESULT_FAILURE; } return KisImageBuilder_RESULT_OK; } bool KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return false; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return false; } bool success = false; if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! success = dev.write(s.data(), s.size()); store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); // Success return success; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } bool KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return false; } bool ret = preview.save(&io, "PNG"); io.close(); return ret; } bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8))); return false; } dbgUI << "File" << filename << " loaded and parsed"; return true; } bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return false; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return false; } if (!root.hasChildNodes()) { m_doc->setErrorMessage(i18n("The file has no layers.")); return false; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // Legacy from the multi-image .kra file period. for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } return true; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return false; } } } return false; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_image->unblockUpdates(); bool retval = true; if (!m_kraLoader->warningMessages().isEmpty()) { m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); retval = true; } if (retval) { m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); } return retval; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index 164d72da9a..ed0191b979 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1154 +1,1154 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(image->proofingConfiguration()->proofingModel, image->proofingConfiguration()->proofingDepth, proofingData); if (proofingProfile->valid()){ //if (KoColorSpaceEngineRegistry::instance()->get("icc")) { // KoColorSpaceEngineRegistry::instance()->get("icc")->addProfile(proofingProfile->fileName()); //} KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image, parent); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); - if (node->inherits("KisLayer") && child.childNodesCount() > 0) { + if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element, parent); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element, parent); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element, parent); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element, parent); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, parent, colorSpace); else if (nodeType == FILE_LAYER) { node = loadFileLayer(element, image, name, opacity); } else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; layer->setUseInTimeline(timelineEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = basePath + QDir::separator() + filename; // Entering the event loop to show the messagebox will delete the image, so up the ref by one image->ref(); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeBasedDocumentBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisCloneInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisCloneInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisCloneInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent, const KoColorSpace *colorSpace) { Q_UNUSED(parent); KisColorizeMaskSP mask = new KisColorizeMask(); bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } diff --git a/plugins/impex/psd/psd_export.cc b/plugins/impex/psd/psd_export.cc index 2448292e4a..e5369ca4b8 100644 --- a/plugins/impex/psd/psd_export.cc +++ b/plugins/impex/psd/psd_export.cc @@ -1,98 +1,98 @@ /* * 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 "psd_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "psd_saver.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_psd_export.json", registerPlugin();) psdExport::psdExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } psdExport::~psdExport() { } KisImportExportFilter::ConversionStatus psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { PSDSaver psdSaver(document); KisImageBuilder_Result res; if ((res = psdSaver.buildFile(io)) == KisImageBuilder_RESULT_OK) { dbgFile <<"success !"; return KisImportExportFilter::OK; } dbgFile <<" Result =" << res; return KisImportExportFilter::InternalError; } void psdExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("PSDLayerStyleCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); - //addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED)); + addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::UNSUPPORTED, i18nc("image conversion warning", "Your image contains one or more layers with a color model that is different from the image."))); ImageSizeCheckFactory *factory = dynamic_cast(KisExportCheckRegistry::instance()->get("ImageSizeCheck")); if (factory) { addCapability(factory->create(30000, 30000, KisExportCheckBase::SUPPORTED)); } QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) // << QPair(RGBAColorModelID, Float16BitsColorDepthID) // << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer16BitsColorDepthID) << QPair(LABAColorModelID, Integer8BitsColorDepthID) << QPair(LABAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PSD"); } #include diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp index b0c1759541..319a11f8b7 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp @@ -1,283 +1,289 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,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_duplicateop.h" #include "kis_duplicateop_p.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_duplicateop_settings.h" #include "kis_duplicateop_settings_widget.h" #include "kis_duplicateop_option.h" KisDuplicateOp::KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) , m_node(node) , m_settings(static_cast(const_cast(settings.data()))) { Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); + m_rotationOption.readOptionSetting(settings); + m_sizeOption.resetAllSensors(); + m_rotationOption.resetAllSensors(); + m_healing = settings->getBool(DUPLICATE_HEALING); m_perspectiveCorrection = settings->getBool(DUPLICATE_CORRECT_PERSPECTIVE); m_moveSourcePoint = settings->getBool(DUPLICATE_MOVE_SOURCE_POINT); m_cloneFromProjection = settings->getBool(DUPLICATE_CLONE_FROM_PROJECTION); m_srcdev = source()->createCompositionSourceDevice(); } KisDuplicateOp::~KisDuplicateOp() { } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); if (!m_duplicateStartIsSet) { m_duplicateStartIsSet = true; m_duplicateStart = info.pos(); } KisPaintDeviceSP realSourceDevice; if (m_cloneFromProjection && m_image) { realSourceDevice = m_image->projection(); } else { KisNodeSP externalSourceNode = m_settings->sourceNode(); /** * The saved layer might have been deleted by then, so check if it * still belongs to a graph */ if (!externalSourceNode || !externalSourceNode->graphListener()) { externalSourceNode = m_node; } realSourceDevice = externalSourceNode->projection(); } + qreal rotation = m_rotationOption.apply(info); + qreal scale = m_sizeOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); - KisDabShape shape(scale, 1.0, 0.0); + KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QPoint srcPoint; if (m_moveSourcePoint) { srcPoint = (dstRect.topLeft() - m_settings->offset()).toPoint(); } else { QPointF hotSpot = brush->hotSpot(shape, info); srcPoint = (m_settings->position() - hotSpot).toPoint(); } qint32 sw = dstRect.width(); qint32 sh = dstRect.height(); // Perspective correction ? // if (m_perspectiveCorrection && m_image && m_image->perspectiveGrid()->countSubGrids() == 1) { // Matrix3qreal startM = Matrix3qreal::Identity(); // Matrix3qreal endM = Matrix3qreal::Identity(); // // First look for the grid corresponding to the start point // KisSubPerspectiveGrid* subGridStart = *m_image->perspectiveGrid()->begin(); // QRect r = QRect(0, 0, m_image->width(), m_image->height()); // if (subGridStart) { // startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight()); // } // // Second look for the grid corresponding to the end point // KisSubPerspectiveGrid* subGridEnd = *m_image->perspectiveGrid()->begin(); // if (subGridEnd) { // endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r); // } // // Compute the translation in the perspective transformation space: // QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart)); // QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(m_settings->offset())); // QPointF translat = duplicateStartPositionT - positionStartPaintingT; // KisSequentialIterator dstIt(m_srcdev, QRect(0, 0, sw, sh)); // KisRandomSubAccessorSP srcAcc = realSourceDevice->createRandomSubAccessor(); // //Action // do { // QPointF p = KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + dstRect.x(), dstIt.y() + dstRect.y())) + translat); // srcAcc->moveTo(p); // srcAcc->sampledOldRawData(dstIt.rawData()); // } while (dstIt.nextPixel()); // } // else { KisPainter copyPainter(m_srcdev); copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bitBltOldData(0, 0, realSourceDevice, srcPoint.x(), srcPoint.y(), sw, sh); copyPainter.end(); } // heal ? if (m_healing) { QRect healRect(dstRect); const bool smallWidth = healRect.width() < 3; const bool smallHeight = healRect.height() < 3; if (smallWidth || smallHeight) { healRect.adjust(-smallWidth, -smallHeight, smallWidth, smallHeight); } const int healSW = healRect.width(); const int healSH = healRect.height(); quint16 srcData[4]; quint16 tmpData[4]; QScopedArrayPointer matrix(new qreal[ 3 * healSW * healSH ]); // First divide const KoColorSpace* srcCs = realSourceDevice->colorSpace(); const KoColorSpace* tmpCs = m_srcdev->colorSpace(); KisHLineConstIteratorSP srcIt = realSourceDevice->createHLineConstIteratorNG(healRect.x(), healRect.y() , healSW); KisHLineIteratorSP tmpIt = m_srcdev->createHLineIteratorNG(0, 0, healSW); qreal* matrixIt = matrix.data(); for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { srcCs->toLabA16(srcIt->oldRawData(), (quint8*)srcData, 1); tmpCs->toLabA16(tmpIt->rawData(), (quint8*)tmpData, 1); // Division for (int k = 0; k < 3; k++) { matrixIt[k] = srcData[k] / (qreal)qMax((int)tmpData [k], 1); } srcIt->nextPixel(); tmpIt->nextPixel(); matrixIt += 3; } srcIt->nextRow(); tmpIt->nextRow(); } // Minimize energy { int iter = 0; qreal err; QScopedArrayPointer solution(new qreal[ 3 * healSW * healSH ]); do { err = DuplicateOpUtils::minimizeEnergy(matrix.data(), solution.data(), healSW, healSH); solution.swap(matrix); iter++; } while (err > 0.00001 && iter < 100); } // Finally multiply KisHLineIteratorSP tmpIt2 = m_srcdev->createHLineIteratorNG(0, 0, healSW); matrixIt = &matrix[0]; for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { tmpCs->toLabA16(tmpIt2->rawData(), (quint8*)tmpData, 1); // Multiplication for (int k = 0; k < 3; k++) { tmpData[k] = (int)CLAMP(matrixIt[k] * qMax((int) tmpData[k], 1), 0, 65535); } tmpCs->fromLabA16((quint8*)tmpData, tmpIt2->rawData(), 1); tmpIt2->nextPixel(); matrixIt += 3; } tmpIt2->nextRow(); } } painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_srcdev, dab, dstRect.width(), dstRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale); } diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h index 2c626ec976..19c061dbaa 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h @@ -1,75 +1,77 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 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_DUPLICATEOP_H_ #define KIS_DUPLICATEOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include +#include #include "kis_duplicateop_settings.h" class KisPaintInformation; class QPointF; class KisPainter; class KisDuplicateOp : public KisBrushBasedPaintOp { public: KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisDuplicateOp() override; KisSpacingInformation paintAt(const KisPaintInformation& info) override; private: qreal minimizeEnergy(const qreal* m, qreal* sol, int w, int h); private: KisImageSP m_image; KisNodeSP m_node; KisDuplicateOpSettingsSP m_settings; KisPaintDeviceSP m_srcdev; KisPaintDeviceSP m_target; QPointF m_duplicateStart; bool m_duplicateStartIsSet; KisPressureSizeOption m_sizeOption; + KisPressureRotationOption m_rotationOption; bool m_healing; bool m_perspectiveCorrection; bool m_moveSourcePoint; bool m_cloneFromProjection; }; #endif // KIS_DUPLICATEOP_H_ diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp index 342be01e21..ca768c2957 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings_widget.cpp @@ -1,75 +1,77 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 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_duplicateop_settings_widget.h" #include "kis_duplicateop_settings.h" #include "kis_duplicateop_option.h" #include #include #include #include #include +#include #include #include #include "kis_texture_option.h" #include "kis_curve_option_widget.h" #include #include "kis_pressure_texture_strength_option.h" #include KisDuplicateOpSettingsWidget::KisDuplicateOpSettingsWidget(QWidget* parent) : KisBrushBasedPaintopOptionWidget(parent) { setObjectName("brush option widget"); setPrecisionEnabled(true); addPaintOpOption(new KisCompositeOpOption(true), i18n("Blending Mode")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureOpacityOption(), i18n("Transparent"), i18n("Opaque")), i18n("Opacity")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")), i18n("Size")); + addPaintOpOption(new KisCurveOptionWidget(new KisPressureRotationOption(), i18n("-180°"), i18n("180°")), i18n("Rotation")); addPaintOpOption(new KisPressureMirrorOptionWidget(), i18n("Mirror")); addPaintOpOption(new KisDuplicateOpOption(), i18n("Painting Mode")); addPaintOpOption(new KisTextureOption(), i18n("Pattern")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength")); } KisDuplicateOpSettingsWidget::~KisDuplicateOpSettingsWidget() { } KisPropertiesConfigurationSP KisDuplicateOpSettingsWidget::configuration() const { KisDuplicateOpSettings *config = new KisDuplicateOpSettings(); config->setOptionsWidget(const_cast(this)); config->setProperty("paintop", "duplicate"); // XXX: make this a const id string writeConfiguration(config); return config; } KisPaintopLodLimitations KisDuplicateOpSettingsWidget::lodLimitations() const { KisPaintopLodLimitations l = KisBrushBasedPaintopOptionWidget::lodLimitations(); l.blockers << KoID("clone-brush", i18nc("PaintOp instant preview limitation", "Clone Brush (temporarily disabled)")); return l; } diff --git a/plugins/tools/basictools/kis_tool_brush.cc b/plugins/tools/basictools/kis_tool_brush.cc index 1ab772f75e..cac06cc1b4 100644 --- a/plugins/tools/basictools/kis_tool_brush.cc +++ b/plugins/tools/basictools/kis_tool_brush.cc @@ -1,468 +1,469 @@ /* * kis_tool_brush.cc - part of Krita * * Copyright (c) 2003-2004 Boudewijn Rempt * Copyright (c) 2015 Moritz Molch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_brush.h" #include #include #include #include #include #include #include #include #include #include "kis_cursor.h" #include "kis_config.h" #include "kis_slider_spin_box.h" #include "kundo2magicstring.h" #define MAXIMUM_SMOOTHNESS_DISTANCE 1000.0 // 0..1000.0 == weight in gui #define MAXIMUM_MAGNETISM 1000 void KisToolBrush::addSmoothingAction(int enumId, const QString &id, const QString &name, const QIcon &icon, KActionCollection *globalCollection) { /** * KisToolBrush is the base of several tools, but the actions * should be unique, so let's be careful with them */ if (!globalCollection->action(id)) { QAction *action = new QAction(name, globalCollection); action->setIcon(icon); globalCollection->addAction(id, action); } QAction *action = dynamic_cast(globalCollection->action(id)); addAction(id, action); connect(action, SIGNAL(triggered()), &m_signalMapper, SLOT(map())); m_signalMapper.setMapping(action, enumId); } KisToolBrush::KisToolBrush(KoCanvasBase * canvas) : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.png", 5, 5), kundo2_i18n("Freehand Brush Stroke")) { setObjectName("tool_brush"); connect(this, SIGNAL(smoothingTypeChanged()), this, SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); addSmoothingAction(KisSmoothingOptions::NO_SMOOTHING, "set_no_brush_smoothing", i18nc("@action", "Brush Smoothing: Disabled"), KisIconUtils::loadIcon("smoothing-no"), collection); addSmoothingAction(KisSmoothingOptions::SIMPLE_SMOOTHING, "set_simple_brush_smoothing", i18nc("@action", "Brush Smoothing: Basic"), KisIconUtils::loadIcon("smoothing-basic"), collection); addSmoothingAction(KisSmoothingOptions::WEIGHTED_SMOOTHING, "set_weighted_brush_smoothing", i18nc("@action", "Brush Smoothing: Weighted"), KisIconUtils::loadIcon("smoothing-weighted"), collection); addSmoothingAction(KisSmoothingOptions::STABILIZER, "set_stabilizer_brush_smoothing", i18nc("@action", "Brush Smoothing: Stabilizer"), KisIconUtils::loadIcon("smoothing-stabilizer"), collection); } KisToolBrush::~KisToolBrush() { } void KisToolBrush::activate(ToolActivation activation, const QSet &shapes) { KisToolFreehand::activate(activation, shapes); connect(&m_signalMapper, SIGNAL(mapped(int)), SLOT(slotSetSmoothingType(int)), Qt::UniqueConnection); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolBrush::deactivate() { disconnect(&m_signalMapper, 0, this, 0); KisToolFreehand::deactivate(); } int KisToolBrush::smoothingType() const { return smoothingOptions()->smoothingType(); } bool KisToolBrush::smoothPressure() const { return smoothingOptions()->smoothPressure(); } int KisToolBrush::smoothnessQuality() const { return smoothingOptions()->smoothnessDistance(); } qreal KisToolBrush::smoothnessFactor() const { return smoothingOptions()->tailAggressiveness(); } void KisToolBrush::slotSetSmoothingType(int index) { /** * The slot can also be called from smoothing-type-switching * action that would mean the combo box will not be synchronized */ if (m_cmbSmoothingType->currentIndex() != index) { m_cmbSmoothingType->setCurrentIndex(index); } switch (index) { case 0: smoothingOptions()->setSmoothingType(KisSmoothingOptions::NO_SMOOTHING); showControl(m_sliderSmoothnessDistance, false); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, false); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 1: smoothingOptions()->setSmoothingType(KisSmoothingOptions::SIMPLE_SMOOTHING); showControl(m_sliderSmoothnessDistance, false); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, false); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 2: smoothingOptions()->setSmoothingType(KisSmoothingOptions::WEIGHTED_SMOOTHING); showControl(m_sliderSmoothnessDistance, true); showControl(m_sliderTailAggressiveness, true); showControl(m_chkSmoothPressure, true); showControl(m_chkUseScalableDistance, true); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 3: default: smoothingOptions()->setSmoothingType(KisSmoothingOptions::STABILIZER); showControl(m_sliderSmoothnessDistance, true); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, true); showControl(m_sliderDelayDistance, true); showControl(m_chkFinishStabilizedCurve, true); showControl(m_chkStabilizeSensors, true); } emit smoothingTypeChanged(); } void KisToolBrush::slotSetSmoothnessDistance(qreal distance) { smoothingOptions()->setSmoothnessDistance(distance); emit smoothnessQualityChanged(); } void KisToolBrush::slotSetTailAgressiveness(qreal argh_rhhrr) { smoothingOptions()->setTailAggressiveness(argh_rhhrr); emit smoothnessFactorChanged(); } // used with weighted smoothing void KisToolBrush::setSmoothPressure(bool value) { smoothingOptions()->setSmoothPressure(value); } void KisToolBrush::slotSetMagnetism(int magnetism) { m_magnetism = expf(magnetism / (double)MAXIMUM_MAGNETISM) / expf(1.0); } bool KisToolBrush::useScalableDistance() const { return smoothingOptions()->useScalableDistance(); } // used with weighted smoothing void KisToolBrush::setUseScalableDistance(bool value) { smoothingOptions()->setUseScalableDistance(value); emit useScalableDistanceChanged(); } void KisToolBrush::resetCursorStyle() { KisConfig cfg; CursorStyle cursorStyle = cfg.newCursorStyle(); // When the stabilizer is in use, we avoid using the brush outline cursor, // because it would hide the real position of the cursor to the user, // yielding unexpected results. if (smoothingOptions()->smoothingType() == KisSmoothingOptions::STABILIZER && smoothingOptions()->useDelayDistance() && cursorStyle == CURSOR_STYLE_NO_CURSOR) { useCursor(KisCursor::roundCursor()); } else { KisToolFreehand::resetCursorStyle(); } overrideCursorIfNotEditable(); } // stabilizer brush settings bool KisToolBrush::useDelayDistance() const { return smoothingOptions()->useDelayDistance(); } qreal KisToolBrush::delayDistance() const { return smoothingOptions()->delayDistance(); } void KisToolBrush::setUseDelayDistance(bool value) { smoothingOptions()->setUseDelayDistance(value); m_sliderDelayDistance->setEnabled(value); enableControl(m_chkFinishStabilizedCurve, !value); emit useDelayDistanceChanged(); } void KisToolBrush::setDelayDistance(qreal value) { smoothingOptions()->setDelayDistance(value); emit delayDistanceChanged(); } void KisToolBrush::setFinishStabilizedCurve(bool value) { smoothingOptions()->setFinishStabilizedCurve(value); emit finishStabilizedCurveChanged(); } bool KisToolBrush::finishStabilizedCurve() const { return smoothingOptions()->finishStabilizedCurve(); } void KisToolBrush::setStabilizeSensors(bool value) { smoothingOptions()->setStabilizeSensors(value); emit stabilizeSensorsChanged(); } bool KisToolBrush::stabilizeSensors() const { return smoothingOptions()->stabilizeSensors(); } void KisToolBrush::updateSettingsViews() { m_cmbSmoothingType->setCurrentIndex(smoothingOptions()->smoothingType()); m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance()); m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance()); m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance()); m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness()); m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure()); m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance()); m_cmbSmoothingType->setCurrentIndex((int)smoothingOptions()->smoothingType()); m_chkStabilizeSensors->setChecked(smoothingOptions()->stabilizeSensors()); emit smoothnessQualityChanged(); emit smoothnessFactorChanged(); emit smoothPressureChanged(); emit smoothingTypeChanged(); emit useScalableDistanceChanged(); emit useDelayDistanceChanged(); emit delayDistanceChanged(); emit finishStabilizedCurveChanged(); emit stabilizeSensorsChanged(); KisTool::updateSettingsViews(); } QWidget * KisToolBrush::createOptionWidget() { QWidget *optionsWidget = KisToolFreehand::createOptionWidget(); optionsWidget->setObjectName(toolId() + "option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); optionsWidget->layout()->addWidget(specialSpacer); // Line smoothing configuration m_cmbSmoothingType = new QComboBox(optionsWidget); m_cmbSmoothingType->addItems(QStringList() << i18n("None") << i18n("Basic") << i18n("Weighted") << i18n("Stabilizer")); connect(m_cmbSmoothingType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetSmoothingType(int))); addOptionWidgetOption(m_cmbSmoothingType, new QLabel(i18n("Brush Smoothing:"))); m_sliderSmoothnessDistance = new KisDoubleSliderSpinBox(optionsWidget); m_sliderSmoothnessDistance->setRange(3.0, MAXIMUM_SMOOTHNESS_DISTANCE, 1); m_sliderSmoothnessDistance->setEnabled(true); connect(m_sliderSmoothnessDistance, SIGNAL(valueChanged(qreal)), SLOT(slotSetSmoothnessDistance(qreal))); m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance()); addOptionWidgetOption(m_sliderSmoothnessDistance, new QLabel(i18n("Distance:"))); // Finish stabilizer curve m_chkFinishStabilizedCurve = new QCheckBox(optionsWidget); m_chkFinishStabilizedCurve->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkFinishStabilizedCurve->sizeHint().height())); connect(m_chkFinishStabilizedCurve, SIGNAL(toggled(bool)), this, SLOT(setFinishStabilizedCurve(bool))); m_chkFinishStabilizedCurve->setChecked(smoothingOptions()->finishStabilizedCurve()); // Delay Distance for Stabilizer QWidget* delayWidget = new QWidget(optionsWidget); QHBoxLayout* delayLayout = new QHBoxLayout(delayWidget); delayLayout->setContentsMargins(0,0,0,0); delayLayout->setSpacing(1); QLabel* delayLabel = new QLabel(i18n("Delay:"), optionsWidget); delayLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); delayLayout->addWidget(delayLabel); m_chkDelayDistance = new QCheckBox(optionsWidget); m_chkDelayDistance->setLayoutDirection(Qt::RightToLeft); delayWidget->setToolTip(i18n("Delay the brush stroke to make the line smoother")); connect(m_chkDelayDistance, SIGNAL(toggled(bool)), this, SLOT(setUseDelayDistance(bool))); delayLayout->addWidget(m_chkDelayDistance); m_sliderDelayDistance = new KisDoubleSliderSpinBox(optionsWidget); m_sliderDelayDistance->setToolTip(i18n("Radius where the brush is blocked")); m_sliderDelayDistance->setRange(0, 500); m_sliderDelayDistance->setSuffix(i18n(" px")); connect(m_sliderDelayDistance, SIGNAL(valueChanged(qreal)), SLOT(setDelayDistance(qreal))); addOptionWidgetOption(m_sliderDelayDistance, delayWidget); addOptionWidgetOption(m_chkFinishStabilizedCurve, new QLabel(i18n("Finish line:"))); m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance()); m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance()); // if the state is not flipped, then the previous line doesn't generate any signals setUseDelayDistance(m_chkDelayDistance->isChecked()); // Stabilize sensors m_chkStabilizeSensors = new QCheckBox(optionsWidget); m_chkStabilizeSensors->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkStabilizeSensors->sizeHint().height())); connect(m_chkStabilizeSensors, SIGNAL(toggled(bool)), this, SLOT(setStabilizeSensors(bool))); m_chkStabilizeSensors->setChecked(smoothingOptions()->stabilizeSensors()); addOptionWidgetOption(m_chkStabilizeSensors, new QLabel(i18n("Stabilize Sensors:"))); m_sliderTailAggressiveness = new KisDoubleSliderSpinBox(optionsWidget); m_sliderTailAggressiveness->setRange(0.0, 1.0, 2); m_sliderTailAggressiveness->setEnabled(true); connect(m_sliderTailAggressiveness, SIGNAL(valueChanged(qreal)), SLOT(slotSetTailAgressiveness(qreal))); m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness()); addOptionWidgetOption(m_sliderTailAggressiveness, new QLabel(i18n("Stroke Ending:"))); m_chkSmoothPressure = new QCheckBox(optionsWidget); m_chkSmoothPressure->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkSmoothPressure->sizeHint().height())); m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure()); connect(m_chkSmoothPressure, SIGNAL(toggled(bool)), this, SLOT(setSmoothPressure(bool))); addOptionWidgetOption(m_chkSmoothPressure, new QLabel(QString("%1:").arg(i18n("Smooth Pressure")))); m_chkUseScalableDistance = new QCheckBox(optionsWidget); m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance()); m_chkUseScalableDistance->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkUseScalableDistance->sizeHint().height())); m_chkUseScalableDistance->setToolTip(i18nc("@info:tooltip", "Scalable distance takes zoom level " "into account and makes the distance " "be visually constant whatever zoom " "level is chosen")); connect(m_chkUseScalableDistance, SIGNAL(toggled(bool)), this, SLOT(setUseScalableDistance(bool))); addOptionWidgetOption(m_chkUseScalableDistance, new QLabel(QString("%1:").arg(i18n("Scalable Distance")))); // add a line spacer so we know that the next set of options are for different settings QFrame* line = new QFrame(optionsWidget); line->setObjectName(QString::fromUtf8("line")); line->setFrameShape(QFrame::HLine); addOptionWidgetOption(line); // Drawing assistant configuration QWidget* assistantWidget = new QWidget(optionsWidget); QGridLayout* assistantLayout = new QGridLayout(assistantWidget); assistantLayout->setContentsMargins(10,0,0,0); assistantLayout->setSpacing(5); m_chkAssistant = new QCheckBox(optionsWidget); m_chkAssistant->setText(i18n("Snap to Assistants")); assistantWidget->setToolTip(i18n("You need to add Ruler Assistants before this tool will work.")); connect(m_chkAssistant, SIGNAL(toggled(bool)), this, SLOT(setAssistant(bool))); - assistantLayout->addWidget(m_chkAssistant, 1, 1, 1, 1, Qt::AlignLeft); + addOptionWidgetOption(assistantWidget, m_chkAssistant); m_sliderMagnetism = new KisSliderSpinBox(optionsWidget); m_sliderMagnetism->setToolTip(i18n("Assistant Magnetism")); m_sliderMagnetism->setRange(0, MAXIMUM_MAGNETISM); m_sliderMagnetism->setValue(m_magnetism * MAXIMUM_MAGNETISM); connect(m_sliderMagnetism, SIGNAL(valueChanged(int)), SLOT(slotSetMagnetism(int))); QAction *toggleaction = new QAction(i18n("Toggle Assistant"), this); addAction("toggle_assistant", toggleaction); toggleaction->setShortcut(QKeySequence(Qt::ControlModifier + Qt::ShiftModifier + Qt::Key_L)); connect(toggleaction, SIGNAL(triggered(bool)), m_chkAssistant, SLOT(toggle())); - - addOptionWidgetOption(m_sliderMagnetism, assistantWidget); + QLabel* magnetismLabel = new QLabel(i18n("Magnetism:")); + addOptionWidgetOption(m_sliderMagnetism, magnetismLabel); QLabel* snapSingleLabel = new QLabel(i18n("Snap Single:")); m_chkOnlyOneAssistant = new QCheckBox(optionsWidget); m_chkOnlyOneAssistant->setToolTip(i18nc("@info:tooltip","Make it only snap to a single assistant, prevents snapping mess while using the infinite assistants.")); m_chkOnlyOneAssistant->setCheckState(Qt::Checked);//turn on by default. connect(m_chkOnlyOneAssistant, SIGNAL(toggled(bool)), this, SLOT(setOnlyOneAssistantSnap(bool))); addOptionWidgetOption(m_chkOnlyOneAssistant, snapSingleLabel); // set the assistant snapping options to hidden by default and toggle their visibility based based off snapping checkbox m_sliderMagnetism->setVisible(false); m_chkOnlyOneAssistant->setVisible(false); snapSingleLabel->setVisible(false); + magnetismLabel->setVisible(false); connect(m_chkAssistant, SIGNAL(toggled(bool)), m_sliderMagnetism, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), m_chkOnlyOneAssistant, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), snapSingleLabel, SLOT(setVisible(bool))); - + connect(m_chkAssistant, SIGNAL(toggled(bool)), magnetismLabel, SLOT(setVisible(bool))); KisConfig cfg; slotSetSmoothingType(cfg.lineSmoothingType()); return optionsWidget; } diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc index bcb1607db8..770498df96 100644 --- a/plugins/tools/basictools/kis_tool_line.cc +++ b/plugins/tools/basictools/kis_tool_line.cc @@ -1,374 +1,369 @@ /* * kis_tool_line.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_line.h" #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_canvas2.h" #include #include #include #include "kis_painting_information_builder.h" #include "kis_tool_line_helper.h" #define ENABLE_RECORDING const KisCoordinatesConverter* getCoordinatesConverter(KoCanvasBase * canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); return kritaCanvas->coordinatesConverter(); } KisToolLine::KisToolLine(KoCanvasBase * canvas) : KisToolShape(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)), m_showGuideline(true), m_strokeIsRunning(false), m_infoBuilder(new KisConverterPaintingInformationBuilder(getCoordinatesConverter(canvas))), m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))), m_strokeUpdateCompressor(500, KisSignalCompressor::POSTPONE), m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_line"); setSupportOutline(true); connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisToolLine::~KisToolLine() { } -int KisToolLine::flags() const -{ - return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET; -} - void KisToolLine::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolLine::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolLine::deactivate() { KisToolPaint::deactivate(); cancelStroke(); } QWidget* KisToolLine::createOptionWidget() { QWidget* widget = KisToolPaint::createOptionWidget(); m_chkUseSensors = new QCheckBox(i18n("Use sensors")); addOptionWidgetOption(m_chkUseSensors); m_chkShowPreview = new QCheckBox(i18n("Show Preview")); addOptionWidgetOption(m_chkShowPreview); m_chkShowGuideline = new QCheckBox(i18n("Show Guideline")); addOptionWidgetOption(m_chkShowGuideline); // hook up connections for value changing connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) ); connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) ); connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) ); // read values in from configuration m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true)); m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true)); m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true)); return widget; } void KisToolLine::setUseSensors(bool value) { configGroup.writeEntry("useSensors", value); } void KisToolLine::setShowGuideline(bool value) { m_showGuideline = value; configGroup.writeEntry("showGuideline", value); } void KisToolLine::setShowPreview(bool value) { configGroup.writeEntry("showPreview", value); } void KisToolLine::requestStrokeCancellation() { cancelStroke(); } void KisToolLine::requestStrokeEnd() { // Terminate any in-progress strokes if (nodePaintAbility() == PAINT && m_helper->isRunning()) { endStroke(); } } void KisToolLine::updatePreviewTimer(bool showGuideline) { // If the user disables the guideline, we will want to try to draw some // preview lines even if they're slow, so set the timer to FIRST_ACTIVE. if (showGuideline) { m_strokeUpdateCompressor.setMode(KisSignalCompressor::POSTPONE); } else { m_strokeUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } } void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if(mode() == KisTool::PAINT_MODE) { paintLine(gc,QRect()); } KisToolPaint::paint(gc,converter); } void KisToolLine::beginPrimaryAction(KoPointerEvent *event) { NodePaintAbility nodeAbility = nodePaintAbility(); if (nodeAbility == NONE || !nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); // Always show guideline on vector layers m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT; updatePreviewTimer(m_showGuideline); m_helper->setEnabled(nodeAbility == PAINT); m_helper->setUseSensors(m_chkUseSensors->isChecked()); m_helper->start(event, canvas()->resourceManager()); m_startPoint = convertToPixelCoordAndSnap(event); m_endPoint = m_startPoint; m_lastUpdatedPoint = m_startPoint; m_strokeIsRunning = true; } void KisToolLine::updateStroke() { if (!m_strokeIsRunning) return; m_helper->repaintLine(canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolLine::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeIsRunning) return; // First ensure the old guideline is deleted updateGuideline(); QPointF pos = convertToPixelCoordAndSnap(event); if (event->modifiers() == Qt::AltModifier) { QPointF trans = pos - m_endPoint; m_helper->translatePoints(trans); m_startPoint += trans; m_endPoint += trans; } else if (event->modifiers() == Qt::ShiftModifier) { pos = straightLine(pos); m_helper->addPoint(event, pos); } else { m_helper->addPoint(event, pos); } m_endPoint = pos; // Draw preview if requested if (m_chkShowPreview->isChecked()) { // If the cursor has moved a significant amount, immediately clear the // current preview and redraw. Otherwise, do slow redraws periodically. auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength(); if (updateDistance > 10) { m_helper->clearPaint(); m_longStrokeUpdateCompressor.stop(); m_strokeUpdateCompressor.start(); m_lastUpdatedPoint = pos; } else if (updateDistance > 1) { m_longStrokeUpdateCompressor.start(); } } updateGuideline(); KisToolPaint::requestUpdateOutline(event->point, event); } void KisToolLine::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); updateGuideline(); endStroke(); } void KisToolLine::endStroke() { NodePaintAbility nodeAbility = nodePaintAbility(); if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == NONE) { return; } if (nodeAbility == PAINT) { updateStroke(); m_helper->end(); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_startPoint)); path->lineTo(resolutionMatrix.map(m_endPoint)); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); KUndo2Command * cmd = canvas()->shapeController()->addShape(path); canvas()->addCommand(cmd); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } void KisToolLine::cancelStroke() { if (!m_strokeIsRunning) return; if (m_startPoint == m_endPoint) return; /** * The actual stroke is run by the timer so it is a legal * situation when m_strokeIsRunning is true, but the actual redraw * stroke is not running. */ if (m_helper->isRunning()) { m_helper->cancel(); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } QPointF KisToolLine::straightLine(QPointF point) { const QPointF lineVector = point - m_startPoint; qreal lineAngle = std::atan2(lineVector.y(), lineVector.x()); if (lineAngle < 0) { lineAngle += 2 * M_PI; } const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24; const quint32 constrainedLineIndex = static_cast((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5); const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES; const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y())); const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle)); const QPointF result = m_startPoint + constrainedLineVector; return result; } void KisToolLine::updateGuideline() { if (canvas()) { QRectF bound(m_startPoint, m_endPoint); canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3))); } } void KisToolLine::paintLine(QPainter& gc, const QRect&) { QPointF viewStartPos = pixelToView(m_startPoint); QPointF viewStartEnd = pixelToView(m_endPoint); if (m_showGuideline && canvas()) { QPainterPath path; path.moveTo(viewStartPos); path.lineTo(viewStartEnd); paintToolOutline(&gc, path); } } QString KisToolLine::quickHelp() const { return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines"); } diff --git a/plugins/tools/basictools/kis_tool_line.h b/plugins/tools/basictools/kis_tool_line.h index e9db2097f2..281b4b0356 100644 --- a/plugins/tools/basictools/kis_tool_line.h +++ b/plugins/tools/basictools/kis_tool_line.h @@ -1,137 +1,136 @@ /* * kis_tool_line.h - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_LINE_H_ #define KIS_TOOL_LINE_H_ #include "kis_tool_shape.h" #include #include #include #include "kis_global.h" #include "kis_types.h" #include "KoToolFactoryBase.h" #include "flake/kis_node_shape.h" #include "kis_signal_compressor.h" #include #include class QPoint; class KoCanvasBase; class QCheckBox; class KisPaintingInformationBuilder; class KisToolLineHelper; class KisToolLine : public KisToolShape { Q_OBJECT public: KisToolLine(KoCanvasBase * canvas); ~KisToolLine() override; void requestStrokeCancellation() override; void requestStrokeEnd() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; - int flags() const override; void paint(QPainter& gc, const KoViewConverter &converter) override; QString quickHelp() const override; protected Q_SLOTS: void resetCursorStyle() override; private Q_SLOTS: void updateStroke(); void setUseSensors(bool value); void setShowPreview(bool value); void setShowGuideline(bool value); private: void paintLine(QPainter& gc, const QRect& rc); QPointF straightLine(QPointF point); void updateGuideline(); void updatePreviewTimer(bool showGuide); QWidget* createOptionWidget() override; void endStroke(); void cancelStroke(); private: bool m_showGuideline; QPointF m_startPoint; QPointF m_endPoint; QPointF m_lastUpdatedPoint; bool m_strokeIsRunning; QCheckBox *m_chkUseSensors; QCheckBox *m_chkShowPreview; QCheckBox *m_chkShowGuideline; QScopedPointer m_infoBuilder; QScopedPointer m_helper; KisSignalCompressor m_strokeUpdateCompressor; KisSignalCompressor m_longStrokeUpdateCompressor; KConfigGroup configGroup; }; class KisToolLineFactory : public KoToolFactoryBase { public: KisToolLineFactory() : KoToolFactoryBase("KritaShape/KisToolLine") { setToolTip(i18n("Line Tool")); // Temporarily setSection(TOOL_TYPE_SHAPE); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setPriority(1); setIconName(koIconNameCStr("krita_tool_line")); } ~KisToolLineFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolLine(canvas); } }; #endif //KIS_TOOL_LINE_H_ diff --git a/plugins/tools/basictools/kis_tool_multihand.cpp b/plugins/tools/basictools/kis_tool_multihand.cpp index 1b4cdeb39b..2c79761413 100644 --- a/plugins/tools/basictools/kis_tool_multihand.cpp +++ b/plugins/tools/basictools/kis_tool_multihand.cpp @@ -1,411 +1,420 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_multihand.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_tool_multihand_helper.h" static const int MAXIMUM_BRUSHES = 50; #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas) : KisToolBrush(canvas), m_transformMode(SYMMETRY), m_angle(0), m_handsCount(6), m_mirrorVertically(false), m_mirrorHorizontally(false), m_showAxes(false), m_translateRadius(100), m_setupAxesFlag(false) , customUI(0) { m_helper = new KisToolMultihandHelper(paintingInformationBuilder(), kundo2_i18n("Multibrush Stroke"), recordingAdapter()); resetHelper(m_helper); if (image()) { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); } } KisToolMultihand::~KisToolMultihand() { } void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event) { if(m_setupAxesFlag) { setMode(KisTool::OTHER); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { initTransformations(); KisToolFreehand::beginPrimaryAction(event); } } void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { KisToolFreehand::continuePrimaryAction(event); } } void KisToolMultihand::endPrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { setMode(KisTool::HOVER_MODE); requestUpdateOutline(event->point, 0); finishAxesSetup(); } else { KisToolFreehand::endPrimaryAction(event); } } void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter) { if(m_setupAxesFlag) { int diagonal = (currentImage()->height() + currentImage()->width()); QPainterPath path; path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle), m_axesPoint.y()-diagonal*sin(m_angle)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle), m_axesPoint.y()+diagonal*sin(m_angle)); path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()-diagonal*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()+diagonal*sin(m_angle+M_PI_2)); paintToolOutline(&gc, pixelToView(path)); } else { KisToolFreehand::paint(gc, converter); if(m_showAxes){ int diagonal = (currentImage()->height() + currentImage()->width()); QPainterPath path; path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle), m_axesPoint.y()-diagonal*sin(m_angle)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle), m_axesPoint.y()+diagonal*sin(m_angle)); path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()-diagonal*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()+diagonal*sin(m_angle+M_PI_2)); paintToolOutline(&gc, pixelToView(path)); } } } void KisToolMultihand::initTransformations() { QVector transformations; QTransform m; if(m_transformMode == SYMMETRY) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount; for(int i = 0; i < m_handsCount; i++) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep; } } else if(m_transformMode == MIRROR) { transformations << m; if (m_mirrorHorizontally) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically && m_mirrorHorizontally){ m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if(m_transformMode == SNOWFLAKE) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount/4; for(int i = 0; i < m_handsCount*4; i++) { if ((i%2)==1) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.scale(-1,1); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } else { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } } } else /* if(m_transformationNode == TRANSLATE) */ { /** * TODO: currently, the seed is the same for all the * strokes */ for (int i = 0; i < m_handsCount; i++){ qreal angle = drand48() * M_PI * 2; qreal length = drand48(); // convert the Polar coordinates to Cartesian coordinates qreal nx = (m_translateRadius * cos(angle) * length); qreal ny = (m_translateRadius * sin(angle) * length); m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.translate(nx,ny); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } m_helper->setupTransformations(transformations); } QWidget* KisToolMultihand::createOptionWidget() { QWidget *widget = KisToolBrush::createOptionWidget(); customUI = new KisToolMultiHandConfigWidget(); // brush smoothing option. customUI->layout()->addWidget(widget); customUI->smoothingOptionsLayout->addWidget(widget); // setup common parameters that all of the modes will see connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool))); customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false)); customUI->moveOriginButton->setCheckable(true); connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup())); + connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes())); + customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY)); // axis mode customUI->multihandTypeCombobox->addItem(i18n("Mirror"),int(MIRROR)); customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE)); customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE)); connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int))); customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0)); slotSetTransformMode(customUI->multihandTypeCombobox->currentIndex()); customUI->axisRotationSpinbox->setSuffix(QChar(Qt::Key_degree)); // origin rotation customUI->axisRotationSpinbox->setRange(0.0, 90.0); customUI->axisRotationSpinbox->setValue(m_configGroup.readEntry("axesAngle", 0.0)); connect( customUI->axisRotationSpinbox, SIGNAL(valueChanged(int)),this, SLOT(slotSetAxesAngle(int))); // symmetry mode options customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES); connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int))); customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4)); // mirror mode specific options connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool))); customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false)); connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool))); customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false)); // translate mode options customUI->translationRadiusSpinbox->setRange(0, 200); customUI->translationRadiusSpinbox->setSuffix(i18n(" px")); customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0)); connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int))); // snowflake re-uses the existing options, so there is no special parameters for that... return static_cast(customUI); // keeping it in the native class until the end allows us to access the UI components } void KisToolMultihand::activateAxesPointModeSetup() { if (customUI->moveOriginButton->isChecked()){ m_setupAxesFlag = true; useCursor(KisCursor::crossCursor()); updateCanvas(); } else { finishAxesSetup(); } } +void KisToolMultihand::resetAxes() +{ + m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); + finishAxesSetup(); +} + + void KisToolMultihand::finishAxesSetup() { m_setupAxesFlag = false; customUI->moveOriginButton->setChecked(false); resetCursorStyle(); updateCanvas(); } void KisToolMultihand::updateCanvas() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); kisCanvas->updateCanvas(); } void KisToolMultihand::slotSetHandsCount(int count) { m_handsCount = count; m_configGroup.writeEntry("handsCount", count); } void KisToolMultihand::slotSetAxesAngle(int angle) { //negative so axes rotates counter clockwise m_angle = -angle*M_PI/180; updateCanvas(); m_configGroup.writeEntry("axesAngle", angle); } void KisToolMultihand::slotSetTransformMode(int index) { m_transformMode = enumTransforModes(customUI->multihandTypeCombobox->itemData(index).toInt()); m_configGroup.writeEntry("transformMode", index); // hide all of the UI elements by default customUI->horizontalCheckbox->setVisible(false); customUI->verticalCheckbox->setVisible(false); customUI->translationRadiusSpinbox->setVisible(false); customUI->radiusLabel->setVisible(false); customUI->brushCountSpinBox->setVisible(false); customUI->brushesLabel->setVisible(false); // turn on what we need if (index == int(MIRROR)) { customUI->horizontalCheckbox->setVisible(true); customUI->verticalCheckbox->setVisible(true); } if (index == int(TRANSLATE)) { customUI->translationRadiusSpinbox->setVisible(true); customUI->radiusLabel->setVisible(true); } if (index == int(SYMMETRY) || index == int(SNOWFLAKE) || index == int(TRANSLATE) ) { customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } } void KisToolMultihand::slotSetAxesVisible(bool vis) { m_showAxes = vis; updateCanvas(); m_configGroup.writeEntry("showAxes", vis); } void KisToolMultihand::slotSetMirrorVertically(bool mirror) { m_mirrorVertically = mirror; m_configGroup.writeEntry("mirrorVertically", mirror); } void KisToolMultihand::slotSetMirrorHorizontally(bool mirror) { m_mirrorHorizontally = mirror; m_configGroup.writeEntry("mirrorHorizontally", mirror); } void KisToolMultihand::slotSetTranslateRadius(int radius) { m_translateRadius = radius; m_configGroup.writeEntry("translateRadius", radius); } diff --git a/plugins/tools/basictools/kis_tool_multihand.h b/plugins/tools/basictools/kis_tool_multihand.h index ed096bc726..93b0e68c62 100644 --- a/plugins/tools/basictools/kis_tool_multihand.h +++ b/plugins/tools/basictools/kis_tool_multihand.h @@ -1,120 +1,121 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * 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 __KIS_TOOL_MULTIHAND_H #define __KIS_TOOL_MULTIHAND_H #include "kis_tool_brush.h" #include #include "kis_tool_multihand_config.h" class QPushButton; class QCheckBox; class QComboBox; class QStackedWidget; class KisSliderSpinBox; class KisToolMultihandHelper; class KisToolMultihand : public KisToolBrush { Q_OBJECT public: KisToolMultihand(KoCanvasBase *canvas); ~KisToolMultihand() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; protected: void paint(QPainter& gc, const KoViewConverter &converter) override; QWidget* createOptionWidget() override; private: void initTransformations(); void finishAxesSetup(); void updateCanvas(); private Q_SLOTS: void activateAxesPointModeSetup(); + void resetAxes(); void slotSetHandsCount(int count); void slotSetAxesAngle(int angle); void slotSetTransformMode(int qcomboboxIndex); void slotSetAxesVisible(bool vis); void slotSetMirrorVertically(bool mirror); void slotSetMirrorHorizontally(bool mirror); void slotSetTranslateRadius(int radius); private: KisToolMultihandHelper *m_helper; enum enumTransforModes { SYMMETRY, MIRROR, TRANSLATE, SNOWFLAKE }; enumTransforModes m_transformMode; QPointF m_axesPoint; qreal m_angle; int m_handsCount; bool m_mirrorVertically; bool m_mirrorHorizontally; bool m_showAxes; int m_translateRadius; bool m_setupAxesFlag; QComboBox * m_transformModesComboBox; KisSliderSpinBox *m_handsCountSlider; KisDoubleSliderSpinBox *m_axesAngleSlider; QCheckBox *m_axesChCkBox; QStackedWidget *m_modeCustomOption; QCheckBox *m_mirrorVerticallyChCkBox; QCheckBox *m_mirrorHorizontallyChCkBox; KisSliderSpinBox *m_translateRadiusSlider; QPushButton *m_axesPointBtn; KisToolMultiHandConfigWidget* customUI; }; class KisToolMultiBrushFactory : public KoToolFactoryBase { public: KisToolMultiBrushFactory() : KoToolFactoryBase("KritaShape/KisToolMultiBrush") { setToolTip(i18n("Multibrush Tool")); // Temporarily setSection(TOOL_TYPE_SHAPE); setIconName(koIconNameCStr("krita_tool_multihand")); setShortcut(QKeySequence(Qt::Key_Q)); setPriority(11); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolMultiBrushFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMultihand(canvas); } }; #endif /* __KIS_TOOL_MULTIHAND_H */ diff --git a/plugins/tools/basictools/wdgmovetool.ui b/plugins/tools/basictools/wdgmovetool.ui index d3e6cb9995..4191e70a6c 100644 --- a/plugins/tools/basictools/wdgmovetool.ui +++ b/plugins/tools/basictools/wdgmovetool.ui @@ -1,261 +1,342 @@ WdgMoveTool 0 0 - 271 - 394 + 245 + 517 - - + + + + + 221 + 101 + + Selection Mode Move the layer that you have currently selected in the layerbox with its masks. Shortcut: ctrl-click. &Move current layer true Move the first layer with visible content at the place where you click. This will also select that layer in the layerbox. Mo&ve layer with content false Move the group containing the first layer that contains visible content. Shortcut: ctrl-shift-click. Move &the whole group - - - - + + + + 227 + 91 + + - Shortcut Move Distance + Move Shortcut - - + + + + + 0 + 0 + + - Number of pixels to move after move shortcut keypress. + When holding shift, move keyboard shortcuts scale up by this amount. 1.000000000000000 - 100000.000000000000000 + 1000.000000000000000 - 1.000000000000000 + 10.000000000000000 - + + + + + 0 + 0 + + + + Multiplier: + + + + - - + + + + + 0 + 0 + + - When holding shift, move keyboard shortcuts scale up by this amount. + Number of pixels to move after move shortcut keypress. + + + px 1.000000000000000 - 1000.000000000000000 + 100000.000000000000000 - 10.000000000000000 + 1.000000000000000 - - + + + + + 0 + 0 + + + + Amount: + + + + + + + + 0 + 0 + + - Large Move Scale + Unit: - - - - Show coordinates on canvas - - - Show coordinates - - - - + - + 0 0 - 240 - 70 + 100 + 101 Position false false false - 2 + 5 5 - 0 + 5 0 QFormLayout::AllNonFixedFieldsGrow + + 5 + + + 5 + + + 5 + + + 5 + 0 0 Horizontal Translation Qt::LeftToRight &x: translateXBox - + 0 0 Horizontal Translation Qt::LeftToRight - + 0 0 Vertical Translation 0 0 Vertical Translation &y: translateYBox - - + + + + Show coordinates on canvas + + + Show coordinates on canvas + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + KisDoubleParseSpinBox QDoubleSpinBox
kis_double_parse_spin_box.h
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
diff --git a/plugins/tools/basictools/wdgmultihandtool.ui b/plugins/tools/basictools/wdgmultihandtool.ui index f8d2a974f4..b4aed1bbd0 100644 --- a/plugins/tools/basictools/wdgmultihandtool.ui +++ b/plugins/tools/basictools/wdgmultihandtool.ui @@ -1,176 +1,258 @@ WdgMultiHandTool 0 0 - 212 - 293 + 200 + 282 + + + 200 + 0 + + 0 10 - + + + QFrame::Plain + + + Qt::Horizontal + + + + + 4 7 - + + + + 0 + 0 + + Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + + true + + + + 0 + 0 + + + + Move + + + false + + + + + + + true + + + + 0 + 0 + + + + Reset + + + false + + + + + + + + + + + + + 0 + 0 + + - Brushes: + Rotation: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Radius: + + + + + 0 + 0 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + 15 + - - + + - - + + - + 0 0 - Rotation: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Show Origin - - - - Vertical + + + + + 0 + 0 + - - - - - Horizontal + Brushes: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - true - + + - + 0 0 - Move Origin - - - false + Horizontal - - + + - + 0 0 - - - 0 - 15 - + + Qt::LeftToRight + + + Origin: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + Vertical + + - - + + - + 0 0 - Show Origin + Radius: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::Vertical 20 40 KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index de663a9234..c2794169b0 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,253 +1,245 @@ /* * kis_tool_select_contiguous - part of Krayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2012 José Luis Vergara * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_select_contiguous.h" #include #include #include #include #include #include -#include #include #include #include #include "KoPointerEvent.h" #include "KoViewConverter.h" #include "kis_cursor.h" #include "kis_selection_manager.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "kis_layer.h" #include "kis_selection_options.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "kis_paint_device.h" #include "kis_pixel_selection.h" #include "tiles3/kis_hline_iterator.h" KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas) : KisToolSelectBase(canvas, KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6), i18n("Contiguous Area Selection")), m_fuzziness(20), m_sizemod(0), m_feather(0), m_limitToCurrentLayer(false) { setObjectName("tool_select_contiguous"); connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, this, &KisToolSelectContiguous::setSelectionAction); } KisToolSelectContiguous::~KisToolSelectContiguous() { } void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } QApplication::setOverrideCursor(KisCursor::waitCursor()); QPoint pos = convertToIntPixelCoord(event); QRect rc = currentImage()->bounds(); KisFillPainter fillpainter(dev); fillpainter.setHeight(rc.height()); fillpainter.setWidth(rc.width()); fillpainter.setFillThreshold(m_fuzziness); fillpainter.setFeather(m_feather); fillpainter.setSizemod(m_sizemod); KisImageWSP image = currentImage(); KisPaintDeviceSP sourceDevice = m_limitToCurrentLayer ? dev : image->projection(); image->lock(); KisSelectionSP selection = fillpainter.createFloodSelection(pos.x(), pos.y(), sourceDevice); image->unlock(); // If we're not antialiasing, threshold the entire selection if (!antiAliasSelection()) { QRect r = selection->selectedExactRect(); if (r.isValid()) { KisHLineIteratorSP selectionIt = selection->pixelSelection()->createHLineIteratorNG(r.x(), r.y(), r.width()); for (qint32 y = 0; y < r.height(); y++) { do { if (selectionIt->rawData()[0] > 0) { selection->pixelSelection()->colorSpace()->setOpacity(selectionIt->rawData(), OPACITY_OPAQUE_U8, 1); } } while (selectionIt->nextPixel()); selectionIt->nextRow(); } } } KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas || !selection->pixelSelection()) { QApplication::restoreOverrideCursor(); return; } selection->pixelSelection()->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area")); helper.selectPixelSelection(selection->pixelSelection(), selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisToolSelectContiguous::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } void KisToolSelectContiguous::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("sizemod", sizemod); } void KisToolSelectContiguous::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("feather", feather); } QWidget* KisToolSelectContiguous::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); selectionWidget->disableSelectionModeOption(); QVBoxLayout * l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); if (l) { - QHBoxLayout * hbox = new QHBoxLayout(); - Q_CHECK_PTR(hbox); - l->insertLayout(1, hbox); + + QGridLayout * gridLayout = new QGridLayout(); + l->insertLayout(1, gridLayout); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); - hbox->addWidget(lbl); + gridLayout->addWidget(lbl, 0, 0, 1, 1); KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(input); input->setObjectName("fuzziness"); input->setRange(1, 100); input->setSingleStep(1); input->setExponentRatio(2); - hbox->addWidget(input); - - hbox = new QHBoxLayout(); - Q_CHECK_PTR(hbox); - l->insertLayout(2, hbox); + gridLayout->addWidget(input, 0, 1, 1, 1); lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget); - hbox->addWidget(lbl); + gridLayout->addWidget(lbl, 1, 0, 1, 1); KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(sizemod); sizemod->setObjectName("sizemod"); //grow/shrink selection sizemod->setRange(-40, 40); sizemod->setSingleStep(1); - hbox->addWidget(sizemod); - - hbox = new QHBoxLayout(); - Q_CHECK_PTR(hbox); - l->insertLayout(3, hbox); + gridLayout->addWidget(sizemod, 1, 1, 1, 1); - hbox->addWidget(new QLabel(i18n("Feathering radius: "), selectionWidget)); + lbl = new QLabel(i18n("Feathering radius: "), selectionWidget); + gridLayout->addWidget(lbl, 2, 0, 1, 1); KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(feather); feather->setObjectName("feathering"); feather->setRange(0, 40); feather->setSingleStep(1); - hbox->addWidget(feather); + gridLayout->addWidget(feather, 2, 1, 1, 1); connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int) )); connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int) )); connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int) )); QCheckBox* limitToCurrentLayer = new QCheckBox(i18n("Limit to current layer"), selectionWidget); l->insertWidget(4, limitToCurrentLayer); connect (limitToCurrentLayer, SIGNAL(stateChanged(int)), this, SLOT(slotLimitToCurrentLayer(int))); // load configuration settings into tool options input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink sizemod->setSuffix(i18n(" px")); feather->setValue(m_configGroup.readEntry("feather", 0)); feather->setSuffix(i18n(" px")); limitToCurrentLayer->setChecked(m_configGroup.readEntry("limitToCurrentLayer", false)); } return selectionWidget; } void KisToolSelectContiguous::slotLimitToCurrentLayer(int state) { if (state == Qt::PartiallyChecked) return; m_limitToCurrentLayer = (state == Qt::Checked); m_configGroup.writeEntry("limitToCurrentLayer", state); } void KisToolSelectContiguous::setSelectionAction(int action) { changeSelectionAction(action); } diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui index 42c99afe2f..5fe5de96b5 100644 --- a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui +++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui @@ -1,148 +1,162 @@ KisToolSmartPatchOptionsWidget 0 0 - 340 - 235 + 250 + 129 - + - - - - 200 - 200 - - - - Inpaint Tool - - - - true - - - - 20 - 40 - 281 - 81 - - - - - 200 - 80 - - + + + + + + + + + low/fast + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::LeftToRight + + + high/slow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + - - - 10 - 16 - 260 - 21 - + + + 0 + 0 + 25 25 50 Qt::Horizontal QSlider::TicksBelow 25 - - - - 190 - 40 - 71 - 20 - - - - Qt::LeftToRight + + + + + + 0 + 0 + + + + Accuracy: + + + + + + + + + + + + 0 + 0 + - Accurate + Patch Radius: + + + patchRadiusLabel + + + + + + + + 0 + 0 + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 20 - 40 - 41 - 17 - + + px - - Fast + + 2 + + + 8 + + + 4 - - - - - 20 - 153 - 181 - 27 - - - - Patch Radius - - - label - - - - - - 220 - 153 - 61 - 27 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 2 - - - 8 - - - 4 - - - + + + + + + + Qt::Vertical + + + + 20 + 40 + + +